"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/main.ts
var main_exports = {};
__export(main_exports, {
default: () => ModelWeavePlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian8 = require("obsidian");
// src/core/dfd-object-scene.ts
function buildDfdObjectScene(object) {
const nodes = /* @__PURE__ */ new Map();
const warnings = [];
nodes.set(object.id, {
id: object.id,
ref: object.id,
kind: object.kind,
object
});
const diagram = {
fileType: "dfd-diagram",
schema: "dfd_diagram",
path: object.path,
title: `${object.name} related`,
frontmatter: {
type: "dfd_diagram",
id: `${object.id}-related`,
name: `${object.name} related`
},
sections: {},
sourceLinks: [],
id: `${object.id}-related`,
name: `${object.name} related`,
kind: "dfd",
domainSources: [],
domains: [],
objectRefs: Array.from(nodes.keys()),
objectEntries: [
{
id: object.id,
label: object.name,
kind: object.kind,
ref: object.id,
rowIndex: 0,
compatibilityMode: "explicit"
}
],
nodes: Array.from(nodes.values()).map(({ object: ignored, ...node }) => node),
edges: [],
flows: []
};
return {
diagram,
nodes: Array.from(nodes.values()),
edges: [],
missingObjects: [],
warnings
};
}
// src/core/domain-relationships.ts
var CONFLICT_FIELDS = ["name", "kind", "parent"];
function buildDomainRelationshipSummaries(model, index) {
const currentDomainsById = new Map(model.domains.map((domain) => [domain.id, domain]));
const childrenByParent = /* @__PURE__ */ new Map();
for (const domain of model.domains) {
if (!domain.parent || !currentDomainsById.has(domain.parent)) {
continue;
}
if (!childrenByParent.has(domain.parent)) {
childrenByParent.set(domain.parent, []);
}
childrenByParent.get(domain.parent).push(domain.id);
}
const standaloneDefinitions = collectStandaloneDomainDefinitions(index);
const dfdLocalReferences = collectDfdLocalDomainReferences(index);
const dfdObjectReferences = collectDfdObjectDomainReferences(index);
return model.domains.map((domain) => ({
domain,
parentId: domain.parent,
childIds: [...childrenByParent.get(domain.id) ?? []].sort(compareText),
definedIn: [...standaloneDefinitions.get(domain.id) ?? []].sort(compareByPath),
conflicts: findConflictingFields(standaloneDefinitions.get(domain.id) ?? []),
dfdLocalDomainReferences: [...dfdLocalReferences.get(domain.id) ?? []].sort(compareByPath),
dfdObjectReferences: [...dfdObjectReferences.get(domain.id) ?? []].sort(
compareDfdObjectReference
)
}));
}
function collectStandaloneDomainDefinitions(index) {
const definitions = /* @__PURE__ */ new Map();
for (const model of Object.values(index.modelsByFilePath)) {
if (model.fileType !== "domains") {
continue;
}
for (const domain of model.domains) {
pushMappedValue(definitions, domain.id, {
path: model.path,
domain
});
}
}
return definitions;
}
function collectDfdLocalDomainReferences(index) {
const references = /* @__PURE__ */ new Map();
for (const diagram of getDfdDiagrams(index)) {
for (const domain of diagram.domains ?? []) {
pushMappedValue(references, domain.id, {
path: diagram.path
});
}
}
return references;
}
function collectDfdObjectDomainReferences(index) {
const references = /* @__PURE__ */ new Map();
for (const diagram of getDfdDiagrams(index)) {
for (const entry of diagram.objectEntries) {
const domain = entry.domain?.trim();
if (!domain) {
continue;
}
pushMappedValue(references, domain, {
path: diagram.path,
objectId: entry.id?.trim() || entry.ref?.trim() || String(entry.rowIndex + 1),
label: entry.label?.trim() || void 0
});
}
}
return references;
}
function getDfdDiagrams(index) {
return Object.values(index.modelsByFilePath).filter(
(model) => model.fileType === "dfd-diagram"
);
}
function findConflictingFields(definitions) {
if (definitions.length < 2) {
return [];
}
return CONFLICT_FIELDS.filter((field) => {
const values = new Set(
definitions.map((definition) => definition.domain[field]?.trim() ?? "")
);
return values.size > 1;
});
}
function pushMappedValue(map, key, value) {
if (!map.has(key)) {
map.set(key, []);
}
map.get(key).push(value);
}
function compareByPath(left, right) {
return compareText(left.path, right.path);
}
function compareDfdObjectReference(left, right) {
return compareText(left.path, right.path) || compareText(left.objectId, right.objectId);
}
function compareText(left, right) {
return left.localeCompare(right);
}
// src/core/domain-diagnostics.ts
function formatDomainIdRequiredMessage() {
return "Domain id is required.";
}
function formatDuplicateDomainIdMessage(id) {
return `duplicate Domain id "${id}"`;
}
function formatDomainParentUnknownMessage(parent) {
return `Domain parent "${parent}" is not defined.`;
}
function formatDomainSelfParentMessage(id) {
return `Domain "${id}" cannot use itself as parent.`;
}
function formatDomainParentCycleMessage(chain) {
return `Domain parent cycle detected: ${chain.join(" -> ")}`;
}
function formatDfdLocalDomainMissingSharedMessage(id) {
return `DFD-local Domain "${id}" is not defined in shared Domains.`;
}
function formatDfdLocalDomainFieldMismatchMessage(id, field, localValue, sharedValue) {
return `DFD-local Domain "${id}" has ${field} "${localValue}", but shared Domains define ${field} "${sharedValue}".`;
}
function formatDfdLocalDomainOverridesSourceMessage(id, field, localValue, sourceValue) {
return `DFD-local Domain "${id}" overrides Domain Source ${field} "${sourceValue}" with "${localValue}".`;
}
function formatDfdObjectUnknownLocalDomainMessage(objectId, domainId) {
return `DFD object "${objectId}" references unknown local Domain "${domainId}".`;
}
function formatDfdObjectUnknownDomainMessage(objectId, domainId) {
return `DFD object "${objectId}" references unknown Domain "${domainId}".`;
}
function formatDfdObjectDomainWithoutLocalDomainsMessage(objectId, domainId) {
return `DFD object "${objectId}" references Domain "${domainId}", but this DFD has no local Domains.`;
}
function formatFlowDiagramLocalDomainOverridesSourceMessage(id, field, localValue, sourceValue) {
return `Flow Diagram local Domain "${id}" overrides Domain Source ${field} "${sourceValue}" with "${localValue}".`;
}
function formatFlowDiagramObjectUnknownLocalDomainMessage(objectId, domainId) {
return `Flow Diagram object "${objectId}" references unknown local Domain "${domainId}".`;
}
function formatFlowDiagramObjectUnknownDomainMessage(objectId, domainId) {
return `Flow Diagram object "${objectId}" references unknown Domain "${domainId}".`;
}
function formatStandaloneDomainDuplicateMessage(id) {
return `Domain "${id}" is defined in multiple Domains files.`;
}
function formatStandaloneDomainFieldConflictMessage(id, field) {
return `Domain "${id}" has conflicting ${field} values across Domains files.`;
}
function formatDomainDiagramMissingRefMessage() {
return "Domain Source ref is required.";
}
function formatDomainDiagramUnresolvedSourceMessage(ref) {
return `Domain Source ref "${ref}" could not be resolved. Check the ID or file name.`;
}
function formatDomainDiagramInvalidSourceTypeMessage(ref, fileType) {
return `Domain Source ref "${ref}" resolves to type "${fileType}", but expected type "domains".`;
}
function formatDomainDiagramNoValidSourcesMessage() {
return "Domain Diagram has no valid Domain Sources.";
}
function formatDomainDiagramEmptySourceMessage(ref) {
return `Domain Source ref "${ref}" has no Domain rows.`;
}
function formatDomainDiagramDuplicateDomainMessage(id, earlierSource, laterSource) {
return `Domain "${id}" is defined by multiple Domain Diagram sources: "${earlierSource}" and "${laterSource}".`;
}
function formatDomainDiagramFieldConflictMessage(id, field, earlierSource, laterSource) {
return `Domain "${id}" has conflicting ${field} values between Domain Diagram sources "${earlierSource}" and "${laterSource}".`;
}
function formatAppProcessUnknownDomainMessage(stepId, domainId) {
return `app_process Step "${stepId}" references unknown Domain "${domainId}".`;
}
function formatAppProcessUnknownLocalDomainMessage(stepId, domainId) {
return `app_process Step "${stepId}" references unknown local Domain "${domainId}".`;
}
function formatAppProcessLocalDomainFieldOverrideMessage(id, field) {
return `app_process local Domain "${id}" overrides external Domain ${field}.`;
}
// src/core/reference-resolver.ts
function normalizeReferenceTarget(reference) {
const parsed = parseReferenceValue(reference);
const target = parsed?.target?.trim();
if (target) {
return target;
}
return reference.trim().replace(/\.md$/i, "").replace(/\\/g, "/");
}
function parseReferenceValue(reference) {
const trimmed = reference.trim();
if (!trimmed) {
return null;
}
if (trimmed.startsWith("[[") && trimmed.endsWith("]]")) {
const inner = trimmed.slice(2, -2).trim();
const [targetPart, aliasPart] = splitWikilinkParts(inner);
return {
raw: trimmed,
kind: "wikilink",
target: normalizeLinkTarget(unescapeWikilinkTarget(targetPart ?? "")),
display: unescapeReferenceLabel(aliasPart?.trim() || "") || void 0
};
}
const markdownLinkMatch = trimmed.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
if (markdownLinkMatch) {
const [, label, target] = markdownLinkMatch;
return {
raw: trimmed,
kind: "markdown_link",
target: isExternalLinkTarget(target) ? void 0 : normalizeLinkTarget(target),
display: label.trim() || void 0,
isExternal: isExternalLinkTarget(target)
};
}
return {
raw: trimmed,
kind: "raw",
target: normalizeLinkTarget(trimmed)
};
}
function parseQualifiedRef(reference) {
const trimmed = reference.trim();
if (!trimmed) {
return null;
}
if (trimmed.startsWith("[[")) {
const closeIndex = trimmed.indexOf("]]");
if (closeIndex >= 0) {
const baseRefRaw = trimmed.slice(0, closeIndex + 2);
const remainder = trimmed.slice(closeIndex + 2);
const memberRef = parseQualifiedMemberSuffix(remainder);
return {
raw: trimmed,
baseRefRaw,
memberRef: memberRef || void 0,
hasMemberRef: Boolean(memberRef)
};
}
}
const markdownLinkMatch = trimmed.match(/^\[[^\]]+\]\([^)]+\)/);
if (markdownLinkMatch) {
const baseRefRaw = markdownLinkMatch[0];
const remainder = trimmed.slice(baseRefRaw.length);
const memberRef = parseQualifiedMemberSuffix(remainder);
return {
raw: trimmed,
baseRefRaw,
memberRef: memberRef || void 0,
hasMemberRef: Boolean(memberRef)
};
}
const rawMatch = trimmed.match(/^(.+)\.([A-Za-z0-9_-]+)$/);
if (rawMatch && !isExternalLinkTarget(trimmed)) {
return {
raw: trimmed,
baseRefRaw: rawMatch[1].trim(),
memberRef: rawMatch[2],
hasMemberRef: true
};
}
return {
raw: trimmed,
baseRefRaw: trimmed,
memberRef: void 0,
hasMemberRef: false
};
}
function extractModelReferenceCandidates(value) {
const trimmed = value.trim();
if (!trimmed) {
return [];
}
const qualified = parseQualifiedRef(trimmed);
if (qualified?.hasMemberRef && qualified.memberRef && isLikelySingleModelReference(qualified.baseRefRaw)) {
return [trimmed];
}
const wikilinks = extractWikilinkReferences(trimmed);
if (wikilinks.length > 0) {
return wikilinks;
}
const parsed = parseReferenceValue(trimmed);
if (parsed?.isExternal) {
return [];
}
if (parsed?.kind === "markdown_link") {
return [trimmed];
}
return isLikelySingleModelReference(trimmed) ? [trimmed] : [];
}
function extractWikilinkReferences(value) {
const refs = [];
let index = 0;
while (index < value.length) {
const start = value.indexOf("[[", index);
if (start < 0) {
break;
}
const end = value.indexOf("]]", start + 2);
if (end < 0) {
break;
}
refs.push(value.slice(start, end + 2));
index = end + 2;
}
return refs;
}
function buildReferenceCandidates(reference) {
const normalized = normalizeReferenceTarget(reference);
if (!normalized) {
return [];
}
const basename = getBasename(normalized);
return Array.from(
new Set(
[reference.trim(), normalized, basename].filter(
(value) => Boolean(value && value.trim())
)
)
);
}
function resolveObjectModelReference(reference, index) {
for (const candidate of buildReferenceCandidates(reference)) {
const direct = index.objectsById[candidate];
if (direct) {
return direct;
}
}
const model = findModelByReference(reference, index);
return model?.fileType === "object" ? model : null;
}
function resolveErEntityReference(reference, index) {
for (const candidate of buildReferenceCandidates(reference)) {
const byId = index.erEntitiesById[candidate];
if (byId) {
return byId;
}
const byPhysicalName = index.erEntitiesByPhysicalName[candidate];
if (byPhysicalName) {
return byPhysicalName;
}
}
const model = findModelByReference(reference, index);
return model?.fileType === "er-entity" ? model : null;
}
function resolveDfdObjectReference(reference, index) {
for (const candidate of buildReferenceCandidates(reference)) {
const direct = index.dfdObjectsById[candidate];
if (direct) {
return direct;
}
}
const model = findModelByReference(reference, index);
return model?.fileType === "dfd-object" ? model : null;
}
function resolveDataObjectReference(reference, index) {
for (const candidate of buildReferenceCandidates(reference)) {
const direct = index.dataObjectsById[candidate];
if (direct) {
return direct;
}
}
const model = findModelByReference(reference, index);
return model?.fileType === "data-object" ? model : null;
}
function findModelByReference(reference, index) {
const candidates = buildReferenceCandidates(reference);
for (const candidate of candidates) {
const directPath = index.modelsByFilePath[candidate];
if (directPath) {
return directPath;
}
const markdownPath = `${candidate}.md`;
if (index.modelsByFilePath[markdownPath]) {
return index.modelsByFilePath[markdownPath];
}
}
for (const candidate of candidates) {
const byId = index.objectsById[candidate] ?? index.appProcessesById[candidate] ?? index.screensById[candidate] ?? index.codesetsById[candidate] ?? index.messagesById[candidate] ?? index.rulesById[candidate] ?? index.mappingsById[candidate] ?? index.colorSchemesById?.[candidate] ?? index.dataObjectsById[candidate] ?? index.dfdObjectsById[candidate] ?? index.erEntitiesById[candidate] ?? index.erEntitiesByPhysicalName[candidate] ?? index.relationsFilesById[candidate] ?? index.domainDiagramsById?.[candidate] ?? index.diagramsById[candidate];
if (byId) {
return byId;
}
}
for (const candidate of candidates) {
const normalizedCandidate = candidate.replace(/\\/g, "/");
const withExtension = normalizedCandidate.endsWith(".md") ? normalizedCandidate : `${normalizedCandidate}.md`;
for (const [path2, model] of Object.entries(index.modelsByFilePath)) {
const normalizedPath = path2.replace(/\\/g, "/");
if (normalizedPath === withExtension || normalizedPath.endsWith(`/${withExtension}`) || getBasename(normalizedPath) === getBasename(normalizedCandidate)) {
return model;
}
}
}
return null;
}
function resolveReferenceIdentity(reference, index) {
const parsed = parseReferenceValue(reference);
const model = findModelByReference(reference, index);
return {
raw: reference.trim(),
parsed,
target: parsed?.target,
displayLabel: parsed?.display,
resolvedFile: model?.path,
resolvedId: getResolvedModelId(model),
resolvedModelType: model?.fileType,
resolvedModel: model
};
}
function getQualifiedMemberCandidates(baseReference, index) {
const identity = resolveReferenceIdentity(baseReference, index);
const merged = [];
const seen = /* @__PURE__ */ new Set();
const addCandidates = (candidates) => {
for (const candidate of candidates ?? []) {
const key = [
candidate.ownerPath,
candidate.memberKind,
candidate.memberId,
candidate.sourceSection,
candidate.displayName ?? ""
].join("|");
if (seen.has(key)) {
continue;
}
seen.add(key);
merged.push(candidate);
}
};
if (identity.resolvedId) {
addCandidates(index.membersByOwnerId[identity.resolvedId]);
}
if (identity.resolvedFile) {
addCandidates(index.membersByOwnerPath[identity.resolvedFile]);
}
return merged;
}
function resolveQualifiedMemberReference(reference, index) {
const qualified = parseQualifiedRef(reference) ?? {
raw: reference.trim(),
baseRefRaw: reference.trim(),
memberRef: void 0,
hasMemberRef: false
};
const baseIdentity = resolveReferenceIdentity(qualified.baseRefRaw, index);
const memberResolution = !qualified.memberRef || !baseIdentity.resolvedModel ? "not-requested" : !isResolvedModelFullyParsed(index, baseIdentity.resolvedModel) ? "deferred" : "unresolved";
const member = memberResolution !== "deferred" && qualified.memberRef && baseIdentity.resolvedModel ? getQualifiedMemberCandidates(qualified.baseRefRaw, index).find(
(candidate) => candidate.memberId === qualified.memberRef
) ?? null : null;
return {
qualified,
baseIdentity,
member,
memberResolution: member ? "resolved" : memberResolution
};
}
function isResolvedModelFullyParsed(index, model) {
return Boolean(model?.path && index.state.fullParsedFilePaths[model.path]);
}
function buildReferenceIdentityKeys(identity) {
const targetBasename = identity.target ? getBasename(identity.target) : void 0;
const rawBasename = identity.raw ? getBasename(normalizeLinkTarget(identity.raw)) : void 0;
return Array.from(
new Set(
[
identity.resolvedFile ? `file:${identity.resolvedFile}` : null,
identity.resolvedId ? `id:${identity.resolvedId}` : null,
identity.target ? `target:${identity.target}` : null,
identity.parsed?.target ? `target:${identity.parsed.target}` : null,
targetBasename ? `basename:${targetBasename}` : null,
identity.raw ? `raw:${normalizeLinkTarget(identity.raw)}` : null,
rawBasename ? `basename:${rawBasename}` : null
].filter((value) => Boolean(value))
)
);
}
function referencesMatch(left, right, index) {
const leftKeys = new Set(buildReferenceIdentityKeys(resolveReferenceIdentity(left, index)));
const rightKeys = buildReferenceIdentityKeys(resolveReferenceIdentity(right, index));
return rightKeys.some((key) => leftKeys.has(key));
}
function getReferenceDisplayName(reference, resolvedModel) {
const parsed = parseReferenceValue(reference);
if (parsed?.display) {
return parsed.display;
}
if (resolvedModel) {
return getReferencedModelDisplayName(resolvedModel);
}
if (parsed?.target) {
return getBasename(parsed.target);
}
return parsed?.raw ?? reference.trim();
}
function getReferencedModelDisplayName(model) {
switch (model.fileType) {
case "data-object":
case "app-process":
case "screen":
case "codeset":
case "message":
case "rule":
case "mapping":
case "color-scheme":
case "dfd-object":
case "dfd-diagram":
case "flow-diagram":
case "domains":
case "domain-diagram":
case "object":
return model.name;
case "er-entity":
return model.logicalName || model.physicalName || model.id;
case "diagram":
return model.name;
case "relations":
return model.title ?? model.path;
case "markdown":
default:
return typeof model.frontmatter.name === "string" && model.frontmatter.name || typeof model.frontmatter.title === "string" && model.frontmatter.title || getBasename(model.path);
}
}
function getResolvedModelId(model) {
if (!model) {
return void 0;
}
switch (model.fileType) {
case "object":
return typeof model.frontmatter.id === "string" && model.frontmatter.id.trim() ? model.frontmatter.id.trim() : model.name;
case "er-entity":
case "dfd-object":
case "dfd-diagram":
case "flow-diagram":
case "data-object":
case "app-process":
case "screen":
case "codeset":
case "message":
case "rule":
case "mapping":
case "color-scheme":
case "domains":
case "domain-diagram":
return model.id;
case "diagram":
return model.name;
case "relations":
return typeof model.frontmatter.id === "string" && model.frontmatter.id.trim() ? model.frontmatter.id.trim() : model.title;
case "markdown":
default:
return void 0;
}
}
function getBasename(path2) {
const normalized = path2.replace(/\\/g, "/");
const leaf = normalized.split("/").pop() ?? normalized;
return leaf.replace(/\.md$/i, "");
}
function isLikelySingleModelReference(value) {
const trimmed = value.trim();
if (!trimmed || trimmed.startsWith("#") || /\s/.test(trimmed)) {
return false;
}
const parsed = parseReferenceValue(trimmed);
if (parsed?.isExternal) {
return false;
}
if (parsed?.kind && parsed.kind !== "raw") {
return true;
}
const target = parsed?.target ?? trimmed;
const basename = getBasename(target);
return /^(?:APP|CLASS|CLD|CLS|CODE|CODESET|CS|DATA|DFD|DFDO|FLOW|DOMAIN|DOMAINS|ENT|ERD|MAP|MSG|PROC|REL|RULE|SCR|SRC)[-_][A-Z0-9][A-Z0-9_-]*$/.test(
basename
);
}
function normalizeLinkTarget(value) {
const trimmed = value.trim();
if (!trimmed) {
return "";
}
if (trimmed.startsWith("#") || trimmed.startsWith("^")) {
return trimmed;
}
const withoutHeading = trimmed.split("#", 1)[0].trim();
const withoutBlock = withoutHeading.split("^", 1)[0].trim();
return withoutBlock.replace(/\.md$/i, "").replace(/\\/g, "/");
}
function isExternalLinkTarget(value) {
const trimmed = value.trim();
return /^[A-Za-z][A-Za-z0-9+.-]*:/.test(trimmed);
}
function parseQualifiedMemberSuffix(value) {
const match = value.match(/^\.\s*([A-Za-z0-9_-]+)\s*$/);
return match?.[1] ?? null;
}
function splitWikilinkParts(value) {
for (let index = 0; index < value.length; index += 1) {
const char = value[index];
if (char === "|") {
return [value.slice(0, index), value.slice(index + 1)];
}
if (char === "\\" && value[index + 1] === "|") {
return [value.slice(0, index), value.slice(index + 2)];
}
}
return [value];
}
function unescapeReferenceLabel(value) {
return value.replace(/\\\|/g, "|").trim();
}
function unescapeWikilinkTarget(value) {
return value.replace(/\\\|/g, "|").trim();
}
// src/core/domain-diagram-resolver.ts
var DOMAIN_CONFLICT_FIELDS = ["parent", "kind", "name"];
function resolveDomainDiagram(diagram, index) {
const resolvedSources = resolveDomainSources(
diagram.path,
diagram.domainSources,
index,
{ warnWhenNoValidSources: true }
);
return {
diagram,
domains: resolvedSources.domains,
sourceSummaries: resolvedSources.sourceSummaries,
conflicts: resolvedSources.conflicts,
warnings: resolvedSources.warnings
};
}
function resolveDomainSources(ownerPath, sourceRefs, index, options = {}) {
const warnings = [];
const sourceSummaries = [];
const validSources = [];
for (const sourceRef of sourceRefs) {
const resolved = findModelByReference(sourceRef.ref, index);
if (!resolved) {
sourceSummaries.push({
ref: sourceRef,
status: "unresolved",
domainCount: 0
});
warnings.push(createSourceWarning(
ownerPath,
sourceRef.rowIndex,
formatDomainDiagramUnresolvedSourceMessage(sourceRef.ref),
"unresolved-reference",
"warning"
));
continue;
}
if (resolved.fileType !== "domains") {
sourceSummaries.push({
ref: sourceRef,
resolvedPath: resolved.path,
status: "invalid-type",
domainCount: 0
});
warnings.push(createSourceWarning(
ownerPath,
sourceRef.rowIndex,
formatDomainDiagramInvalidSourceTypeMessage(sourceRef.ref, resolved.fileType),
"invalid-structure",
"warning"
));
continue;
}
sourceSummaries.push({
ref: sourceRef,
resolvedPath: resolved.path,
resolvedId: resolved.id,
status: resolved.domains.length > 0 ? "ok" : "empty",
domainCount: resolved.domains.length
});
validSources.push({ source: resolved, ref: sourceRef.ref });
if (resolved.domains.length === 0) {
warnings.push(createSourceWarning(
ownerPath,
sourceRef.rowIndex,
formatDomainDiagramEmptySourceMessage(sourceRef.ref),
"invalid-structure",
"warning"
));
}
}
if (validSources.length === 0 && options.warnWhenNoValidSources) {
warnings.push({
code: "invalid-structure",
message: formatDomainDiagramNoValidSourcesMessage(),
severity: "warning",
path: ownerPath,
field: "Domain Sources"
});
}
const mergeResult = mergeDomainDiagramSources(validSources, ownerPath);
warnings.push(...mergeResult.warnings);
return {
domains: mergeResult.domains,
sourceSummaries,
conflicts: mergeResult.conflicts,
warnings
};
}
function mergeDomainDiagramSources(sources, diagramPath = "") {
const effectiveById = /* @__PURE__ */ new Map();
const order = [];
const conflicts = [];
const warnings = [];
for (const entry of sources) {
for (const domain of entry.source.domains) {
const previous = effectiveById.get(domain.id);
if (!previous) {
order.push(domain.id);
effectiveById.set(domain.id, {
domain: { ...domain },
sourcePath: entry.source.path
});
continue;
}
const duplicateConflict = createConflict(
domain.id,
"duplicate",
previous.sourcePath,
entry.source.path,
void 0,
void 0,
"error"
);
conflicts.push(duplicateConflict);
warnings.push(createMergeWarning(
diagramPath || entry.source.path,
formatDomainDiagramDuplicateDomainMessage(
domain.id,
previous.sourcePath,
entry.source.path
),
duplicateConflict.severity
));
for (const field of DOMAIN_CONFLICT_FIELDS) {
const previousValue = previous.domain[field]?.trim() ?? "";
const nextValue = domain[field]?.trim() ?? "";
if (previousValue === nextValue) {
continue;
}
const severity = field === "name" ? "warning" : "error";
const conflict = createConflict(
domain.id,
field,
previous.sourcePath,
entry.source.path,
previousValue,
nextValue,
severity
);
conflicts.push(conflict);
warnings.push(createMergeWarning(
diagramPath || entry.source.path,
formatDomainDiagramFieldConflictMessage(
domain.id,
field,
previous.sourcePath,
entry.source.path
),
severity
));
}
effectiveById.set(domain.id, {
domain: { ...domain },
sourcePath: entry.source.path
});
}
}
return {
domains: order.map((id) => effectiveById.get(id)?.domain).filter((domain) => Boolean(domain)),
conflicts,
warnings
};
}
function createConflict(domainId, field, earlierSourcePath, laterSourcePath, earlierValue, laterValue, severity) {
return {
domainId,
field,
earlierSourcePath,
laterSourcePath,
earlierValue,
laterValue,
effectiveSourcePath: laterSourcePath,
severity
};
}
function createSourceWarning(path2, rowIndex, message, code, severity) {
return {
code,
message,
severity,
path: path2,
field: "Domain Sources.ref",
context: { rowIndex: rowIndex + 1 }
};
}
function createMergeWarning(path2, message, severity) {
return {
code: "invalid-structure",
message,
severity,
path: path2,
field: "Domain Sources"
};
}
// src/core/app-process-domain-resolver.ts
var APP_PROCESS_DOMAIN_CONFLICT_FIELDS = ["parent", "kind", "name"];
function resolveAppProcessDomainPlacement(process, index) {
const localDomains = process.domains ?? [];
const hasLocalDomains = localDomains.length > 0;
if (!hasLocalDomains && process.domainSources.length === 0) {
return {
process,
domains: [],
sourceSummaries: [],
conflicts: [],
placements: [],
warnings: []
};
}
const resolvedSources = process.domainSources.length > 0 ? resolveDomainSources(process.path, process.domainSources, index) : {
domains: [],
sourceSummaries: [],
conflicts: [],
warnings: []
};
const mergeResult = mergeAppProcessDomains(
resolvedSources.domains,
localDomains,
process.path
);
const domainsById = new Map(mergeResult.domains.map((domain) => [domain.id, domain]));
const warnings = [
...resolvedSources.warnings,
...mergeResult.warnings,
...validateMergedAppProcessDomainParents(process.path, mergeResult.domains)
];
const conflicts = [
...resolvedSources.conflicts,
...mergeResult.conflicts
];
const placements = (process.steps ?? []).map((step) => {
const domainId = step.domain?.trim() ?? "";
if (!domainId) {
return null;
}
const domain = domainsById.get(domainId);
if (!domain) {
warnings.push({
code: "unresolved-reference",
message: hasLocalDomains && process.domainSources.length === 0 ? formatAppProcessUnknownLocalDomainMessage(step.id, domainId) : formatAppProcessUnknownDomainMessage(step.id, domainId),
severity: "warning",
path: process.path,
field: "Steps.domain",
context: { stepId: step.id, domainId }
});
}
return {
stepId: step.id,
stepLabel: step.label,
domainId,
lane: step.lane,
status: domain ? "resolved" : "unresolved",
domain
};
}).filter(
(placement) => Boolean(placement)
);
return {
process,
domains: mergeResult.domains,
sourceSummaries: resolvedSources.sourceSummaries,
conflicts,
placements,
warnings
};
}
function validateMergedAppProcessDomainParents(processPath, domains) {
const domainIds = new Set(domains.map((domain) => domain.id));
const warnings = [];
for (const domain of domains) {
if (!domain.parent || domain.parent === domain.id || domainIds.has(domain.parent)) {
continue;
}
warnings.push({
code: "unresolved-reference",
message: formatDomainParentUnknownMessage(domain.parent),
severity: "warning",
path: processPath,
field: "Domains.parent",
context: { rowIndex: domain.rowIndex + 1 }
});
}
return warnings;
}
function mergeAppProcessDomains(externalDomains, localDomains, processPath) {
const domainsById = /* @__PURE__ */ new Map();
const order = [];
const conflicts = [];
const warnings = [];
for (const domain of externalDomains) {
if (!domainsById.has(domain.id)) {
order.push(domain.id);
}
domainsById.set(domain.id, { ...domain });
}
for (const localDomain of localDomains) {
const externalDomain = domainsById.get(localDomain.id);
if (!externalDomain) {
order.push(localDomain.id);
domainsById.set(localDomain.id, { ...localDomain });
continue;
}
for (const field of APP_PROCESS_DOMAIN_CONFLICT_FIELDS) {
const externalValue = externalDomain[field]?.trim() ?? "";
const localValue = localDomain[field]?.trim() ?? "";
if (externalValue === localValue) {
continue;
}
conflicts.push({
domainId: localDomain.id,
field,
earlierSourcePath: "Domain Sources",
laterSourcePath: processPath,
earlierValue: externalValue,
laterValue: localValue,
effectiveSourcePath: processPath,
severity: "warning"
});
warnings.push({
code: "invalid-structure",
message: formatAppProcessLocalDomainFieldOverrideMessage(localDomain.id, field),
severity: "warning",
path: processPath,
field: "Domains"
});
}
domainsById.set(localDomain.id, { ...localDomain });
}
return {
domains: order.map((id) => domainsById.get(id)).filter((domain) => Boolean(domain)),
conflicts,
warnings
};
}
// src/core/color-scheme.ts
var BUILT_IN_COLOR_ENTRIES = [
{ target: "domain", kind: "organization", fill: "#e3f2fd", stroke: "#1976d2", text: "#111111", rowIndex: 0 },
{ target: "domain", kind: "department", fill: "#e8f5e9", stroke: "#388e3c", text: "#111111", rowIndex: 1 },
{ target: "domain", kind: "location", fill: "#fff3e0", stroke: "#f57c00", text: "#111111", rowIndex: 2 },
{ target: "domain", kind: "system", fill: "#f3e5f5", stroke: "#7b1fa2", text: "#111111", rowIndex: 3 },
{ target: "domain", kind: "external", fill: "#eeeeee", stroke: "#616161", text: "#111111", rowIndex: 4 },
{ target: "domain", kind: "device", fill: "#e0f7fa", stroke: "#0097a7", text: "#111111", rowIndex: 5 },
{ kind: "default", fill: "#f5f5f5", stroke: "#9e9e9e", text: "#111111", rowIndex: 6 }
];
var BUILT_IN_COLOR_SCHEME = {
id: "built-in-default",
name: "Built-in default",
entries: BUILT_IN_COLOR_ENTRIES,
defaultStyle: {
fill: "#f5f5f5",
stroke: "#9e9e9e",
text: "#111111"
}
};
function resolveDefaultColorScheme(index, defaultColorSchemeRef) {
const ref = defaultColorSchemeRef?.trim();
if (!ref) {
return { colorScheme: BUILT_IN_COLOR_SCHEME, warnings: [] };
}
const resolved = findModelByReference(ref, index);
if (!resolved) {
return {
colorScheme: BUILT_IN_COLOR_SCHEME,
warnings: [createColorSchemeSettingWarning(formatColorSchemeSettingUnresolvedMessage(ref))]
};
}
if (resolved.fileType !== "color-scheme") {
return {
colorScheme: BUILT_IN_COLOR_SCHEME,
warnings: [
createColorSchemeSettingWarning(
formatColorSchemeSettingInvalidTypeMessage(ref, resolved.fileType)
)
]
};
}
return {
colorScheme: {
id: resolved.id,
name: resolved.name,
sourcePath: resolved.path,
entries: resolved.colors,
defaultStyle: resolveColorStyle(BUILT_IN_COLOR_SCHEME, "", "")
},
warnings: []
};
}
function resolveColorStyle(colorScheme, target, kind) {
const scheme = colorScheme ?? BUILT_IN_COLOR_SCHEME;
const normalizedTarget = target.trim().toLowerCase();
const normalizedKind = kind?.trim().toLowerCase() ?? "";
const targetKindMatch = scheme.entries.find(
(entry) => (entry.target?.trim().toLowerCase() ?? "") === normalizedTarget && entry.kind.trim().toLowerCase() === normalizedKind
);
if (targetKindMatch) {
return mergeStyle(scheme.defaultStyle, entryToStyle(targetKindMatch));
}
const globalKindMatch = scheme.entries.find(
(entry) => !entry.target?.trim() && entry.kind.trim().toLowerCase() === normalizedKind
);
if (globalKindMatch) {
return mergeStyle(scheme.defaultStyle, entryToStyle(globalKindMatch));
}
const builtInTargetKindMatch = BUILT_IN_COLOR_SCHEME.entries.find(
(entry) => (entry.target?.trim().toLowerCase() ?? "") === normalizedTarget && entry.kind.trim().toLowerCase() === normalizedKind
);
if (builtInTargetKindMatch) {
return mergeStyle(
BUILT_IN_COLOR_SCHEME.defaultStyle,
entryToStyle(builtInTargetKindMatch)
);
}
const defaultMatch = scheme.entries.find(
(entry) => !entry.target?.trim() && entry.kind.trim().toLowerCase() === "default"
);
if (defaultMatch) {
return mergeStyle(BUILT_IN_COLOR_SCHEME.defaultStyle, entryToStyle(defaultMatch));
}
return BUILT_IN_COLOR_SCHEME.defaultStyle;
}
function getAppliedColorSchemeRowsForTargets(colorScheme, targets) {
const scheme = colorScheme ?? BUILT_IN_COLOR_SCHEME;
const normalizedTargets = new Set(
targets.map((target) => target.trim().toLowerCase()).filter(Boolean)
);
const rowsByKey = /* @__PURE__ */ new Map();
const isBuiltInScheme = scheme.id === BUILT_IN_COLOR_SCHEME.id;
for (const entry of scheme.entries) {
if (isEntryRelevantForTargets(entry, normalizedTargets)) {
rowsByKey.set(entryKey(entry), {
entry,
source: isBuiltInScheme ? "built-in" : "configured"
});
}
}
if (!isBuiltInScheme) {
for (const entry of BUILT_IN_COLOR_SCHEME.entries) {
if (isEntryRelevantForTargets(entry, normalizedTargets)) {
const key = entryKey(entry);
if (!rowsByKey.has(key)) {
rowsByKey.set(key, { entry, source: "built-in" });
}
}
}
}
return [...rowsByKey.values()].sort(compareAppliedColorSchemeRows);
}
function formatColorSchemeKindRequiredMessage() {
return "Color Scheme kind is required.";
}
function formatColorSchemeInvalidColorMessage(field, value) {
return `Color Scheme ${field} "${value}" is not a supported hex color.`;
}
function formatColorSchemeDuplicateEntryMessage(target, kind) {
const targetLabel = target.trim() || "(default target)";
return `duplicate Color Scheme entry for target "${targetLabel}" and kind "${kind}"`;
}
function formatColorSchemeSettingUnresolvedMessage(ref) {
return `Default Color Scheme ref "${ref}" could not be resolved. Built-in colors will be used.`;
}
function formatColorSchemeSettingInvalidTypeMessage(ref, fileType) {
return `Default Color Scheme ref "${ref}" resolves to type "${fileType}", but expected type "color_scheme". Built-in colors will be used.`;
}
function entryToStyle(entry) {
return {
fill: entry.fill,
stroke: entry.stroke,
text: entry.text
};
}
function mergeStyle(base, override) {
return {
fill: override.fill ?? base.fill,
stroke: override.stroke ?? base.stroke,
text: override.text ?? base.text
};
}
function normalizeKind(kind) {
return kind.trim().toLowerCase();
}
function isEntryRelevantForTargets(entry, normalizedTargets) {
const entryTarget = entry.target?.trim().toLowerCase() ?? "";
return !entryTarget || normalizedTargets.has(entryTarget);
}
function entryKey(entry) {
const target = entry.target?.trim().toLowerCase() ?? "";
return `${target}::${normalizeKind(entry.kind)}`;
}
function compareAppliedColorSchemeRows(left, right) {
const leftTarget = left.entry.target?.trim().toLowerCase() ?? "";
const rightTarget = right.entry.target?.trim().toLowerCase() ?? "";
if (leftTarget !== rightTarget) {
if (!leftTarget) {
return -1;
}
if (!rightTarget) {
return 1;
}
return leftTarget.localeCompare(rightTarget);
}
return normalizeKind(left.entry.kind).localeCompare(normalizeKind(right.entry.kind));
}
function createColorSchemeSettingWarning(message) {
return {
code: "unresolved-reference",
message,
severity: "warning",
field: "defaultColorSchemeRef"
};
}
// src/core/object-context-resolver.ts
function resolveObjectContext(object, index) {
return object.fileType === "er-entity" ? resolveErEntityContext(object, index) : resolveClassLikeContext(object, index);
}
function resolveClassLikeContext(object, index) {
const warnings = [];
const seen = /* @__PURE__ */ new Set();
const relatedObjects = [];
const objectId = getObjectId(object);
for (const relation of object.relations) {
const relationKey = buildClassRelationKey(relation);
if (seen.has(relationKey)) {
continue;
}
seen.add(relationKey);
const relatedReference = relation.targetClass;
const relatedObject = resolveObjectModelReference(relatedReference, index) ?? void 0;
const relatedIdentity = relatedObject ? void 0 : resolveReferenceIdentity(relatedReference, index);
const relatedObjectId = relatedObject ? getObjectId(relatedObject) : relatedIdentity?.resolvedId ?? relation.targetClass;
if (!relatedObject && !relatedIdentity?.resolvedModel) {
warnings.push({
code: "unresolved-reference",
message: `unresolved related object "${relatedObjectId}"`,
severity: "warning",
path: object.path,
field: "relatedObjects"
});
}
relatedObjects.push({
relation,
relatedObjectId,
relatedObject,
direction: "outgoing"
});
}
for (const candidate of Object.values(index.objectsById)) {
if (getObjectId(candidate) === objectId) {
continue;
}
for (const relation of candidate.relations) {
const targetObject = resolveObjectModelReference(relation.targetClass, index);
if (!targetObject || getObjectId(targetObject) !== objectId) {
continue;
}
const relationKey = buildClassRelationKey(relation);
if (seen.has(relationKey)) {
continue;
}
seen.add(relationKey);
relatedObjects.push({
relation,
relatedObjectId: getObjectId(candidate),
relatedObject: candidate,
direction: "incoming"
});
}
}
for (const relation of index.relationsByObjectId[objectId] ?? []) {
const relationKey = relation.id ?? buildRelationKey(relation);
if (seen.has(relationKey)) {
continue;
}
seen.add(relationKey);
const outgoing = relation.source === objectId;
const relatedReference = outgoing ? relation.target : relation.source;
const relatedObject = resolveObjectModelReference(relatedReference, index) ?? void 0;
const relatedIdentity = relatedObject ? void 0 : resolveReferenceIdentity(relatedReference, index);
const relatedObjectId = relatedObject ? getObjectId(relatedObject) : relatedIdentity?.resolvedId ?? relatedReference;
if (!relatedObject && !relatedIdentity?.resolvedModel) {
warnings.push({
code: "unresolved-reference",
message: `unresolved related object "${relatedObjectId}"`,
severity: "warning",
path: object.path,
field: "relatedObjects"
});
}
relatedObjects.push({
relation,
relatedObjectId,
relatedObject,
direction: outgoing ? "outgoing" : "incoming"
});
}
return {
object,
relatedObjects,
warnings
};
}
function resolveErEntityContext(object, index) {
const warnings = [];
const seen = /* @__PURE__ */ new Set();
const relatedObjects = [];
const allEntities = Object.values(index.erEntitiesById);
for (const relation of object.outboundRelations) {
const relationKey = relation.id ?? `${object.id}:${relation.targetEntity}:${relation.kind}`;
if (seen.has(relationKey)) {
continue;
}
seen.add(relationKey);
const relatedObject = resolveErEntityReference(relation.targetEntity, index) ?? void 0;
const relatedObjectId = relatedObject?.id ?? relation.targetEntity;
if (!relatedObject) {
warnings.push({
code: "unresolved-reference",
message: `unresolved related entity "${relation.targetEntity}"`,
severity: "warning",
path: object.path,
field: "relatedObjects"
});
}
relatedObjects.push({
relation,
relatedObjectId,
relatedObject,
direction: "outgoing"
});
}
for (const entity of allEntities) {
if (entity.id === object.id) {
continue;
}
for (const relation of entity.outboundRelations) {
const targetEntity = resolveErEntityReference(relation.targetEntity, index);
if (!targetEntity || targetEntity.id !== object.id) {
continue;
}
const relationKey = relation.id ?? `${entity.id}:${relation.targetEntity}:${relation.kind}`;
if (seen.has(relationKey)) {
continue;
}
seen.add(relationKey);
relatedObjects.push({
relation,
relatedObjectId: entity.id,
relatedObject: entity,
direction: "incoming"
});
}
}
return {
object,
relatedObjects,
warnings
};
}
function getObjectId(object) {
const explicitId = object.frontmatter.id;
if (typeof explicitId === "string" && explicitId.trim()) {
return explicitId.trim();
}
return object.name;
}
function buildRelationKey(relation) {
return `${relation.source}:${relation.kind}:${relation.target}:${relation.label ?? ""}`;
}
function buildClassRelationKey(relation) {
return relation.id ?? `${relation.sourceClass}:${relation.targetClass}:${relation.kind}:${relation.label ?? ""}`;
}
// src/parsers/markdown-table.ts
function parseMarkdownTable(lines, expectedHeaders, path2, sectionName) {
if (!lines) {
return { rows: [], warnings: [] };
}
const normalizedLines = lines.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (normalizedLines.length < 2) {
return {
rows: [],
warnings: normalizedLines.length === 0 ? [] : [
createWarning(
"invalid-table-row",
`table in section "${sectionName}" is incomplete`,
path2,
sectionName
)
]
};
}
const headers = splitMarkdownTableRow(normalizedLines[0]) ?? [];
const warnings = [];
if (!sameHeaders(headers, expectedHeaders)) {
warnings.push(
createWarning(
"invalid-table-column",
`table columns in section "${sectionName}" do not match expected headers`,
path2,
sectionName
)
);
}
const rows = [];
for (const rowLine of normalizedLines.slice(2)) {
const values = splitMarkdownTableRow(rowLine) ?? [];
if (isEmptyMarkdownTableDataRow(values)) {
continue;
}
if (values.length !== headers.length) {
warnings.push(
createWarning(
"invalid-table-row",
`table row in section "${sectionName}" has ${values.length} columns, expected ${headers.length}`,
path2,
sectionName
)
);
continue;
}
const row = {};
for (const [index, header] of headers.entries()) {
row[header] = values[index] ?? "";
}
rows.push(row);
}
return { rows, warnings };
}
function isEmptyMarkdownTableDataRow(cells) {
return cells.length > 0 && cells.every((cell) => cell.trim().length === 0);
}
function splitMarkdownTableRow(line) {
const ranges = getMarkdownTableCellRanges(line);
if (!ranges) {
return null;
}
return ranges.map((range) => line.slice(range.contentStart, range.contentEnd).trim());
}
function getMarkdownTableCellRanges(line) {
const trimmedLine = line.trim();
if (!trimmedLine.startsWith("|")) {
return null;
}
const separatorIndexes = findMarkdownTableSeparators(line);
if (separatorIndexes.length === 0) {
return null;
}
const trailingPipeIndex = separatorIndexes[separatorIndexes.length - 1];
const effectiveSeparators = trailingPipeIndex === line.length - 1 ? separatorIndexes : [...separatorIndexes, line.length];
if (effectiveSeparators.length < 2) {
return null;
}
const cells = [];
for (let columnIndex = 0; columnIndex < effectiveSeparators.length - 1; columnIndex += 1) {
const rawStart = effectiveSeparators[columnIndex] + 1;
const rawEnd = effectiveSeparators[columnIndex + 1];
let contentStart = rawStart;
let contentEnd = rawEnd;
while (contentStart < rawEnd && /\s/.test(line[contentStart] ?? "")) {
contentStart += 1;
}
while (contentEnd > rawStart && /\s/.test(line[contentEnd - 1] ?? "")) {
contentEnd -= 1;
}
if (contentStart > contentEnd) {
contentStart = rawStart;
contentEnd = rawStart;
}
cells.push({
columnIndex,
rawStart,
rawEnd,
contentStart,
contentEnd
});
}
return cells;
}
function findMarkdownTableSeparators(line) {
const separators = [];
let escaped = false;
let wikilinkDepth = 0;
let markdownLinkTextDepth = 0;
let markdownLinkTargetDepth = 0;
for (let index = 0; index < line.length; index += 1) {
const char = line[index];
const next = line[index + 1];
if (escaped) {
escaped = false;
continue;
}
if (char === "\\") {
escaped = true;
continue;
}
if (char === "[" && next === "[") {
wikilinkDepth += 1;
index += 1;
continue;
}
if (char === "]" && next === "]" && wikilinkDepth > 0) {
wikilinkDepth -= 1;
index += 1;
continue;
}
if (wikilinkDepth === 0 && markdownLinkTargetDepth === 0 && char === "[") {
markdownLinkTextDepth += 1;
continue;
}
if (markdownLinkTextDepth > 0 && char === "]" && next === "(") {
markdownLinkTextDepth -= 1;
markdownLinkTargetDepth = 1;
index += 1;
continue;
}
if (markdownLinkTargetDepth > 0) {
if (char === "(") {
markdownLinkTargetDepth += 1;
continue;
}
if (char === ")") {
markdownLinkTargetDepth -= 1;
continue;
}
}
if (char === "|" && wikilinkDepth === 0 && markdownLinkTextDepth === 0 && markdownLinkTargetDepth === 0) {
separators.push(index);
continue;
}
}
return separators;
}
function sameHeaders(actual, expected) {
if (actual.length !== expected.length) {
return false;
}
return actual.every((header, index) => header === expected[index]);
}
function createWarning(code, message, path2, field) {
return {
code,
message,
severity: "warning",
path: path2,
field
};
}
// src/i18n/language.ts
var import_obsidian = require("obsidian");
function resolveModelWeaveLanguage(language) {
return language?.toLowerCase().startsWith("ja") ? "ja" : "en";
}
function getModelWeaveLanguage() {
try {
return resolveModelWeaveLanguage((0, import_obsidian.getLanguage)());
} catch {
return "en";
}
}
function isJapaneseLanguage(language) {
return resolveModelWeaveLanguage(language ?? getModelWeaveLanguage()) === "ja";
}
function modelWeaveText(en, ja, language) {
return isJapaneseLanguage(language) ? ja : en;
}
// src/core/diagnostic-section-guidance.ts
var SOURCE_LINKS_HEADER = "| path | notes |";
var DOMAIN_SOURCES_HEADER = "ref | notes";
var DOMAIN_HEADER = "id | name | kind | parent | description";
var SECTION_HEADERS = {
"app-process": {
inputs: "id | data | source | required | notes",
outputs: "id | data | target | notes",
triggers: "id | kind | source | event | notes",
transitions: "id | event | to | condition | notes",
steps: "id | domain | label | kind | input | output | rule | invoke | screen | notes",
flows: "from | to | condition | label | notes",
domains: DOMAIN_HEADER,
"domain sources": DOMAIN_SOURCES_HEADER,
"source links": SOURCE_LINKS_HEADER
},
codeset: {
values: "code | label | sort_order | active | notes",
"source links": SOURCE_LINKS_HEADER
},
"color-scheme": {
colors: "| target | kind | fill | stroke | text | notes |",
"source links": SOURCE_LINKS_HEADER
},
"data-object": {
format: "key | value | notes",
records: "record_type | name | occurrence | notes",
fields: "name | label | type | length | required | path | ref | notes",
"source links": SOURCE_LINKS_HEADER
},
diagram: {
objects: "ref | notes",
relations: "id | from | to | kind | label | from_multiplicity | to_multiplicity | notes",
"source links": SOURCE_LINKS_HEADER
},
"dfd-diagram": {
objects: "id | label | kind | ref | domain | notes",
flows: "id | from | to | data | notes",
domains: DOMAIN_HEADER,
"domain sources": DOMAIN_SOURCES_HEADER,
"source links": SOURCE_LINKS_HEADER
},
"dfd-object": {
"source links": SOURCE_LINKS_HEADER
},
"flow-diagram": {
"domain sources": DOMAIN_SOURCES_HEADER,
domains: DOMAIN_HEADER,
objects: "id | label | kind | ref | domain | notes",
flows: "id | from | to | kind | trigger | data | condition | notes",
"source links": SOURCE_LINKS_HEADER
},
"domain-diagram": {
"domain sources": DOMAIN_SOURCES_HEADER,
"source links": SOURCE_LINKS_HEADER
},
domains: {
domains: DOMAIN_HEADER,
"source links": SOURCE_LINKS_HEADER
},
"er-entity": {
columns: "logical_name | physical_name | data_type | length | scale | not_null | pk | encrypted | default_value | notes",
indexes: "index_name | index_type | unique | columns | notes",
relations: "local_column | target_column | notes",
"source links": SOURCE_LINKS_HEADER
},
mapping: {
scope: "role | ref | notes",
mappings: "target_ref | source_ref | transform | rule | required | notes",
"source links": SOURCE_LINKS_HEADER
},
message: {
messages: "id | text | severity | audience | notes",
"source links": SOURCE_LINKS_HEADER
},
object: {
attributes: "name | type | visibility | static | notes",
methods: "name | parameters | returns | visibility | static | notes",
relations: "id | to | kind | label | from_multiplicity | to_multiplicity | notes",
"source links": SOURCE_LINKS_HEADER
},
relations: {
"source links": SOURCE_LINKS_HEADER
},
rule: {
inputs: "id | data | source | required | notes",
references: "ref | usage | notes",
conditions: "id | expression | severity | message | notes",
outputs: "id | data | target | notes",
"source links": SOURCE_LINKS_HEADER
},
screen: {
layout: "id | label | kind | purpose | notes",
fields: "id | label | kind | layout | data_type | required | ref | condition | rule | notes",
actions: "id | label | kind | target | event | condition | invoke | transition | rule | notes",
messages: "id | text | severity | timing | condition | notes",
transitions: "id | event | to | condition | notes",
steps: "id | label | kind | condition | input | output | rule | invoke | screen | notes",
errors: "id | condition | message | notes",
"source links": SOURCE_LINKS_HEADER
}
};
var FILE_TYPE_ALIASES = {
app_process: "app-process",
appprocess: "app-process",
class: "object",
class_diagram: "diagram",
classdiagram: "diagram",
color_scheme: "color-scheme",
colorscheme: "color-scheme",
data_object: "data-object",
dataobject: "data-object",
dfd_diagram: "dfd-diagram",
dfddiagram: "dfd-diagram",
flow_diagram: "flow-diagram",
flowdiagram: "flow-diagram",
dfd_object: "dfd-object",
dfdobject: "dfd-object",
domain_diagram: "domain-diagram",
domaindiagram: "domain-diagram",
er_diagram: "diagram",
erdiagram: "diagram",
er_entity: "er-entity",
erentity: "er-entity",
model_object_v1: "object",
model_relations_v1: "relations"
};
function attachDiagnosticModelContext(diagnostic, fileType) {
const section = getDiagnosticSectionName(diagnostic);
return {
...diagnostic,
context: {
...diagnostic.context,
fileType,
...section ? { section } : {}
}
};
}
function resolveDiagnosticSectionGuidance(diagnostic) {
if (!isTableHeaderDiagnostic(diagnostic)) {
return null;
}
const fileType = normalizeFileType(getDiagnosticStringValue(diagnostic.context?.fileType));
const section = getDiagnosticSectionName(diagnostic);
return resolveSectionGuidance(fileType, section);
}
function resolveSectionGuidance(fileType, sectionName) {
const normalizedFileType = normalizeFileType(fileType);
const normalizedSection = normalizeSectionName(sectionName);
if (!normalizedFileType || !normalizedSection) {
return null;
}
const expectedHeader = SECTION_HEADERS[normalizedFileType]?.[normalizedSection] ?? getCommonExpectedHeader(normalizedFileType, normalizedSection);
if (expectedHeader) {
return {
fileType: normalizedFileType,
section: sectionName?.trim() || normalizedSection,
supported: true,
expectedHeader,
sectionKind: normalizedSection,
copyExpectedHeaderAvailable: true
};
}
return {
fileType: normalizedFileType,
section: sectionName?.trim() || normalizedSection,
supported: false,
sectionKind: normalizedSection,
manualFix: getUnsupportedSectionManualFix(normalizedFileType, normalizedSection),
copyExpectedHeaderAvailable: false
};
}
function getExpectedHeaderForDiagnostic(diagnostic) {
const guidance = resolveDiagnosticSectionGuidance(diagnostic);
return guidance?.copyExpectedHeaderAvailable ? guidance.expectedHeader ?? null : null;
}
function getUnsupportedSectionManualFix(fileType, section) {
if (fileType === "rule" && (section === "messages" || section === "message")) {
return {
en: 'Section "Messages" is not supported for rule files. Put rule messages in the message column of ## Conditions, or define reusable text in a separate type: message file.',
ja: "rule \u30D5\u30A1\u30A4\u30EB\u3067\u306F ## Messages \u30BB\u30AF\u30B7\u30E7\u30F3\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002rule \u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u306F ## Conditions \u306E message \u5217\u306B\u8A18\u8FF0\u3059\u308B\u304B\u3001\u518D\u5229\u7528\u3059\u308B\u6587\u8A00\u306F type: message \u30D5\u30A1\u30A4\u30EB\u3068\u3057\u3066\u5B9A\u7FA9\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
};
}
if (fileType === "app-process" && (section === "messages" || section === "message")) {
return {
en: 'Section "Messages" is not supported for app_process files. Use ## Errors, Transitions.notes, or define reusable text in a separate type: message file.',
ja: "app_process \u30D5\u30A1\u30A4\u30EB\u3067\u306F ## Messages \u30BB\u30AF\u30B7\u30E7\u30F3\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002## Errors\u3001Transitions.notes\u3001\u307E\u305F\u306F\u518D\u5229\u7528\u3059\u308B\u6587\u8A00\u3092 type: message \u30D5\u30A1\u30A4\u30EB\u3068\u3057\u3066\u5B9A\u7FA9\u3059\u308B\u3053\u3068\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
};
}
return void 0;
}
function getDiagnosticSectionName(diagnostic) {
const contextSection = getDiagnosticStringValue(diagnostic.context?.section);
if (contextSection) {
return getSectionFromField(contextSection) ?? contextSection;
}
const quoted = diagnostic.message.match(/section "([^"]+)"/i)?.[1];
return quoted ?? diagnostic.section ?? getSectionFromField(diagnostic.field);
}
function getSectionFromField(field) {
const section = field?.split(".")[0]?.split(":")[0]?.trim();
return section || null;
}
function isTableHeaderDiagnostic(diagnostic) {
return diagnostic.code === "invalid-table-column" || /table columns in section/i.test(diagnostic.message) || /table should use:/i.test(diagnostic.message) || /do not match expected .*headers/i.test(diagnostic.message) || /do not match supported .*headers/i.test(diagnostic.message);
}
function normalizeSectionName(sectionName) {
const value = sectionName?.trim().toLowerCase();
return value ? value.replace(/\s+/g, " ") : null;
}
function normalizeFileType(value) {
if (!value) {
return null;
}
const raw = value.trim();
if (!raw) {
return null;
}
const lower = raw.toLowerCase();
const alias = FILE_TYPE_ALIASES[lower];
if (alias) {
return alias;
}
const normalized = lower.replace(/_/g, "-");
return isKnownFileType(normalized) ? normalized : null;
}
function getCommonExpectedHeader(fileType, section) {
if (fileType === "markdown") {
return null;
}
if (section === "source links") {
return SOURCE_LINKS_HEADER;
}
if (section === "domain sources" && (fileType === "app-process" || fileType === "dfd-diagram" || fileType === "domain-diagram")) {
return DOMAIN_SOURCES_HEADER;
}
return null;
}
function isKnownFileType(value) {
return [
"object",
"relations",
"diagram",
"data-object",
"app-process",
"screen",
"rule",
"codeset",
"message",
"mapping",
"color-scheme",
"domains",
"domain-diagram",
"dfd-object",
"dfd-diagram",
"flow-diagram",
"er-entity",
"markdown"
].includes(value);
}
function getDiagnosticStringValue(value) {
if (typeof value === "string") {
const trimmed = value.trim();
return trimmed || null;
}
if (Array.isArray(value)) {
return value.map((entry) => String(entry).trim()).filter(Boolean).join(" | ") || null;
}
return null;
}
// src/core/current-file-diagnostics.ts
var CLASS_RELATION_KINDS = /* @__PURE__ */ new Set([
"association",
"dependency",
"inheritance",
"implementation",
"aggregation",
"composition"
]);
function buildCurrentObjectDiagnostics(model, index, context, warnings) {
const diagnostics = warnings.map(
(warning) => normalizeDiagnosticSeverity(attachDiagnosticModelContext(warning, model.fileType))
);
const missingIdDiagnostic = createMissingFrontmatterIdDiagnostic(model, diagnostics);
if (missingIdDiagnostic) {
diagnostics.push(missingIdDiagnostic);
}
diagnostics.push(...buildCommonSectionDiagnostics(model));
if (model.fileType === "object") {
diagnostics.push(...buildClassDiagnostics(model, index));
} else if (model.fileType === "app-process") {
diagnostics.push(...buildAppProcessDiagnostics(model, index));
} else if (model.fileType === "screen") {
diagnostics.push(...buildScreenDiagnostics(model, index));
} else if (model.fileType === "codeset") {
diagnostics.push(...buildCodeSetDiagnostics(model));
} else if (model.fileType === "message") {
diagnostics.push(...buildMessageDiagnostics(model));
} else if (model.fileType === "rule") {
diagnostics.push(...buildRuleDiagnostics(model, index));
} else if (model.fileType === "mapping") {
diagnostics.push(...buildMappingDiagnostics(model, index));
} else if (model.fileType === "dfd-object") {
diagnostics.push(...buildDfdObjectDiagnostics(model));
} else if (model.fileType === "data-object") {
diagnostics.push(...buildDataObjectDiagnostics(model, index));
} else if (model.fileType === "color-scheme") {
} else if (model.fileType === "domains") {
} else {
diagnostics.push(...buildErEntityDiagnostics(model, index));
}
if (context) {
diagnostics.push(
...context.warnings.map(
(warning) => normalizeDiagnosticSeverity(attachDiagnosticModelContext(warning, model.fileType))
)
);
}
return finalizeCurrentDiagnostics(addModelContextToDiagnostics(diagnostics, model));
}
function addModelContextToDiagnostics(diagnostics, model) {
return diagnostics.map((diagnostic) => attachDiagnosticModelContext(diagnostic, model.fileType));
}
function createMissingFrontmatterIdDiagnostic(model, existingDiagnostics) {
const frontmatter = model.frontmatter;
if (!frontmatter || !hasFrontmatterString(frontmatter, "type") || !hasFrontmatterString(frontmatter, "name")) {
return null;
}
if (hasFrontmatterString(frontmatter, "id")) {
return null;
}
if (existingDiagnostics.some(
(diagnostic) => diagnostic.field === "id" && (/required frontmatter "id" is missing/i.test(diagnostic.message) || /frontmatter "id" is missing/i.test(diagnostic.message))
)) {
return null;
}
return {
code: "invalid-structure",
message: 'frontmatter "id" is missing; id is used as the stable model identifier.',
severity: "error",
path: model.path,
field: "id",
context: {
section: "frontmatter",
frontmatterKey: "id",
type: typeof frontmatter.type === "string" ? frontmatter.type : void 0
}
};
}
function hasFrontmatterString(frontmatter, key) {
const value = frontmatter[key];
return typeof value === "string" && value.trim().length > 0;
}
function buildCommonSectionDiagnostics(model) {
return [
...buildSourceLinksTableDiagnostics(model)
];
}
function buildSourceLinksTableDiagnostics(model) {
if (model.fileType === "markdown") {
return [];
}
const sourceLinks = model.sections?.["Source Links"];
if (!sourceLinks) {
return [];
}
const tableLines = sourceLinks.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (tableLines.length === 0) {
return [];
}
const headers = splitMarkdownTableRow(tableLines[0])?.map((header) => header.trim()) ?? [];
if (sameStringList(headers, ["path", "notes"])) {
return [];
}
return [{
code: "invalid-table-column",
message: 'table columns in section "Source Links" do not match expected headers',
severity: "error",
path: model.path,
field: "Source Links",
context: {
section: "Source Links"
}
}];
}
function sameStringList(actual, expected) {
return actual.length === expected.length && actual.every((value, index) => value === expected[index]);
}
function buildCodeSetDiagnostics(model) {
const diagnostics = [];
const codes = /* @__PURE__ */ new Set();
const sortOrders = /* @__PURE__ */ new Set();
if (!model.kind?.trim()) {
diagnostics.push(createSectionWarning(model.path, "kind", "kind is empty"));
}
if (model.values.length === 0) {
diagnostics.push(createSectionWarning(model.path, "Values", "values are empty"));
return diagnostics;
}
for (const value of model.values) {
const code = value.code?.trim();
if (!code) {
diagnostics.push(createSectionError(model.path, "Values", "values.code is empty"));
} else {
if (codes.has(code)) {
diagnostics.push(createSectionError(model.path, "Values", `duplicate code "${code}"`));
}
codes.add(code);
}
if (!value.label?.trim()) {
diagnostics.push(createSectionWarning(model.path, "Values", `label is empty for code "${code ?? "(blank)"}"`));
}
const active = value.active?.trim();
if (!active) {
diagnostics.push(createSectionWarning(model.path, "Values", `active is empty for code "${code ?? "(blank)"}"`));
} else if (active !== "Y" && active !== "N") {
diagnostics.push(createSectionWarning(model.path, "Values", `active must be Y or N for code "${code ?? "(blank)"}"`));
} else if (active === "N") {
diagnostics.push(createSectionInfo(model.path, "Values", `inactive code "${code ?? "(blank)"}" is defined`));
}
const sortOrder = value.sortOrder?.trim();
if (sortOrder) {
if (!/^-?\d+(\.\d+)?$/.test(sortOrder)) {
diagnostics.push(createSectionWarning(model.path, "Values", `sort_order is not numeric for code "${code ?? "(blank)"}"`));
}
if (sortOrders.has(sortOrder)) {
diagnostics.push(createSectionWarning(model.path, "Values", `duplicate sort_order "${sortOrder}"`));
}
sortOrders.add(sortOrder);
} else {
diagnostics.push(createSectionInfo(model.path, "Values", `sort_order is empty for code "${code ?? "(blank)"}"`));
}
if (!value.notes?.trim()) {
diagnostics.push(createSectionInfo(model.path, "Values", `notes are empty for code "${code ?? "(blank)"}"`));
}
}
return diagnostics;
}
function buildMessageDiagnostics(model) {
const diagnostics = [];
const messageIds = /* @__PURE__ */ new Set();
if (!model.kind?.trim()) {
diagnostics.push(createSectionWarning(model.path, "kind", "kind is empty"));
}
if (model.messages.length === 0) {
diagnostics.push(createSectionWarning(model.path, "Messages", "messages are empty"));
return diagnostics;
}
for (const entry of model.messages) {
const messageId = entry.messageId?.trim();
if (!messageId) {
diagnostics.push(createSectionError(model.path, "Messages", "messages.message_id is empty"));
} else {
if (messageIds.has(messageId)) {
diagnostics.push(createSectionError(model.path, "Messages", `duplicate message_id "${messageId}"`));
}
messageIds.add(messageId);
}
if (!entry.text?.trim()) {
diagnostics.push(createSectionError(model.path, "Messages", `text is empty for message_id "${messageId ?? "(blank)"}"`));
}
const severity = entry.severity?.trim();
if (!severity) {
diagnostics.push(createSectionWarning(model.path, "Messages", `severity is empty for message_id "${messageId ?? "(blank)"}"`));
} else if (!["info", "success", "warning", "error", "confirm", "other"].includes(severity)) {
diagnostics.push(createSectionWarning(model.path, "Messages", `severity is invalid for message_id "${messageId ?? "(blank)"}"`));
}
if (!entry.timing?.trim()) {
diagnostics.push(createSectionWarning(model.path, "Messages", `timing is empty for message_id "${messageId ?? "(blank)"}"`));
}
if (!entry.audience?.trim()) {
diagnostics.push(createSectionWarning(model.path, "Messages", `audience is empty for message_id "${messageId ?? "(blank)"}"`));
}
const active = entry.active?.trim();
if (!active) {
diagnostics.push(createSectionWarning(model.path, "Messages", `active is empty for message_id "${messageId ?? "(blank)"}"`));
} else if (active !== "Y" && active !== "N") {
diagnostics.push(createSectionWarning(model.path, "Messages", `active must be Y or N for message_id "${messageId ?? "(blank)"}"`));
} else if (active === "N") {
diagnostics.push(createSectionInfo(model.path, "Messages", `inactive message "${messageId ?? "(blank)"}" is defined`));
}
if (!entry.notes?.trim()) {
diagnostics.push(createSectionInfo(model.path, "Messages", `notes are empty for message_id "${messageId ?? "(blank)"}"`));
}
}
return diagnostics;
}
function buildRuleDiagnostics(model, index) {
const diagnostics = [];
const inputIds = /* @__PURE__ */ new Set();
if (!model.summary?.trim()) {
diagnostics.push(createSectionWarning(model.path, "Summary", "summary is empty"));
}
if (model.inputs.length === 0) {
diagnostics.push(createSectionWarning(model.path, "Inputs", "inputs are empty"));
}
if (!(model.sections.Conditions ?? []).some((line) => line.trim())) {
diagnostics.push(createSectionWarning(model.path, "Conditions", "conditions are empty"));
}
for (const input of model.inputs) {
const id = input.id?.trim();
if (id) {
if (inputIds.has(id)) {
diagnostics.push(createSectionWarning(model.path, "Inputs", `duplicate input id "${id}"`));
}
inputIds.add(id);
}
diagnostics.push(
...buildReferenceWarnings(model.path, "Inputs", input.data, index, "unresolved rule input data reference"),
...buildReferenceWarnings(model.path, "Inputs", input.source, index, "unresolved rule input source reference")
);
}
for (const reference of model.references) {
diagnostics.push(
...buildReferenceWarnings(model.path, "References", reference.ref, index, "unresolved rule reference")
);
}
for (const message of model.messages) {
diagnostics.push(
...buildReferenceWarnings(model.path, "Messages", message.message, index, "unresolved message reference")
);
}
return diagnostics;
}
function buildMappingDiagnostics(model, index) {
const diagnostics = [];
const mappingRows = /* @__PURE__ */ new Set();
const targetMemberRows = /* @__PURE__ */ new Map();
for (const scope of model.scope) {
diagnostics.push(
...buildReferenceWarnings(model.path, "Scope", scope.ref, index, "unresolved scope reference")
);
}
for (const row of model.mappings) {
const targetRef = row.targetRef?.trim();
const sourceRef = row.sourceRef?.trim();
const transform = row.transform?.trim();
const required = row.required?.trim();
const rule = row.rule?.trim();
if (!targetRef) {
diagnostics.push(createSectionWarning(model.path, "Mappings", "target_ref is empty"));
}
if (sourceRef && targetRef) {
const duplicateKey = buildMappingRowDuplicateKey(sourceRef, targetRef, transform, rule);
if (mappingRows.has(duplicateKey)) {
diagnostics.push(
createSectionWarning(
model.path,
"Mappings",
`duplicate mapping row "${formatMappingReferenceForMessage(sourceRef)} -> ${formatMappingReferenceForMessage(targetRef)}"`
)
);
}
mappingRows.add(duplicateKey);
}
if (targetRef) {
const targetMember = getMappingTargetMemberReference(targetRef);
if (targetMember) {
const rowKey = buildMappingRowDuplicateKey(sourceRef, targetRef, transform, rule);
const existing = targetMemberRows.get(targetMember.key) ?? {
display: targetMember.display,
rowKeys: /* @__PURE__ */ new Set(),
warned: false
};
existing.rowKeys.add(rowKey);
if (!existing.warned && existing.rowKeys.size > 1) {
diagnostics.push(createDuplicateMappingTargetMemberWarning(model.path, targetMember.display));
existing.warned = true;
}
targetMemberRows.set(targetMember.key, existing);
}
}
if (!sourceRef && !transform) {
diagnostics.push(createSectionWarning(model.path, "Mappings", "source_ref is empty and transform is also empty"));
}
if (sourceRef) {
diagnostics.push(
...buildReferenceWarnings(model.path, "Mappings", sourceRef, index, "unresolved mapping source_ref")
);
}
if (targetRef) {
diagnostics.push(
...buildReferenceWarnings(model.path, "Mappings", targetRef, index, "unresolved mapping target_ref")
);
}
if (rule) {
diagnostics.push(
...buildReferenceWarnings(model.path, "Mappings", rule, index, "unresolved mapping rule reference")
);
}
if (required && required !== "Y" && required !== "N") {
diagnostics.push(createSectionWarning(model.path, "Mappings", `required must be Y or N for target_ref "${targetRef ?? "(blank)"}"`));
}
}
return diagnostics;
}
function buildMappingRowDuplicateKey(sourceRef, targetRef, transform, rule) {
return [
normalizeMappingReferenceKey(sourceRef),
normalizeMappingReferenceKey(targetRef),
normalizeMappingFreeTextKey(transform),
normalizeMappingReferenceKey(rule)
].join(" ");
}
function getMappingTargetMemberReference(reference) {
const qualified = parseQualifiedRef(reference);
if (!qualified?.hasMemberRef || !qualified.memberRef) {
return null;
}
const display = formatMappingReferenceForMessage(reference);
return {
key: normalizeMappingReferenceKey(reference),
display
};
}
function createDuplicateMappingTargetMemberWarning(path2, display) {
return {
code: "duplicate-mapping-target-member",
message: `mapping target member "${display}" is mapped from multiple sources.`,
severity: "warning",
path: path2,
field: "Mappings",
context: { section: "Mappings", reference: display }
};
}
function normalizeMappingReferenceKey(reference) {
return formatMappingReferenceForMessage(reference).toLowerCase();
}
function normalizeMappingFreeTextKey(value) {
return value?.trim().replace(/\s+/g, " ").toLowerCase() ?? "";
}
function formatMappingReferenceForMessage(reference) {
const trimmed = reference?.trim();
if (!trimmed) {
return "";
}
const qualified = parseQualifiedRef(trimmed);
if (qualified) {
const parsedBase = parseReferenceValue(qualified.baseRefRaw);
const base = parsedBase?.target?.trim() || qualified.baseRefRaw.trim();
return qualified.memberRef ? `${base}.${qualified.memberRef}` : base;
}
const parsed = parseReferenceValue(trimmed);
return parsed?.target?.trim() || trimmed;
}
function buildAppProcessDiagnostics(model, index) {
const diagnostics = [];
const inputIds = /* @__PURE__ */ new Set();
const outputIds = /* @__PURE__ */ new Set();
for (const input of model.inputs) {
const id = input.id?.trim();
if (id) {
if (inputIds.has(id)) {
diagnostics.push(createSectionWarning(model.path, "Inputs", `duplicate input id "${id}"`));
}
inputIds.add(id);
}
diagnostics.push(
...buildReferenceWarnings(model.path, "Inputs", input.data, index, "unresolved input data reference"),
...buildReferenceWarnings(model.path, "Inputs", input.source, index, "unresolved input source reference")
);
}
for (const output of model.outputs) {
const id = output.id?.trim();
if (id) {
if (outputIds.has(id)) {
diagnostics.push(createSectionWarning(model.path, "Outputs", `duplicate output id "${id}"`));
}
outputIds.add(id);
}
diagnostics.push(
...buildReferenceWarnings(model.path, "Outputs", output.data, index, "unresolved output data reference"),
...buildReferenceWarnings(model.path, "Outputs", output.target, index, "unresolved output target reference")
);
}
for (const trigger of model.triggers) {
diagnostics.push(
...buildReferenceWarnings(model.path, "Triggers", trigger.source, index, "unresolved trigger source reference")
);
}
for (const transition of model.transitions) {
diagnostics.push(
...buildReferenceWarnings(
model.path,
"Transitions",
transition.to,
index,
"transition target reference",
void 0,
{ useCouldNotResolveMessage: true }
)
);
}
return diagnostics;
}
function buildScreenDiagnostics(model, index) {
const diagnostics = [];
const layoutIds = /* @__PURE__ */ new Set();
const fieldIds = /* @__PURE__ */ new Set();
const actionIds = /* @__PURE__ */ new Set();
for (const layout of model.layouts) {
const id = layout.id?.trim();
if (!id) {
continue;
}
if (layoutIds.has(id)) {
diagnostics.push(createSectionError(model.path, "Layout", `duplicate layout id "${id}"`));
}
layoutIds.add(id);
}
for (const field of model.fields) {
const id = field.id?.trim();
if (!id) {
diagnostics.push({
code: "invalid-structure",
message: "field id is empty",
severity: "error",
path: model.path,
field: "Fields",
line: field.rowLine,
context: { section: "Fields" }
});
} else {
if (fieldIds.has(id)) {
diagnostics.push(createSectionWarning(model.path, "Fields", `duplicate field id "${id}"`));
}
fieldIds.add(id);
}
const layoutId = field.layout?.trim();
if (layoutId && layoutIds.size > 0 && !layoutIds.has(layoutId)) {
diagnostics.push(createSectionWarning(model.path, "Fields", `field layout "${layoutId}" does not match any Layout.id`));
} else if (!layoutId && layoutIds.size > 0) {
diagnostics.push(createSectionWarning(model.path, "Fields", `layout is empty for field "${id || field.label || "(field)"}"`));
}
diagnostics.push(
...buildReferenceWarnings(model.path, "Fields", field.ref, index, "unresolved field ref"),
...buildReferenceWarnings(model.path, "Fields", field.rule, index, "unresolved field rule reference")
);
}
const actionSignatures = /* @__PURE__ */ new Set();
let hasTransitionAction = false;
for (const action of model.actions) {
const id = action.id?.trim();
if (id) {
if (actionIds.has(id)) {
diagnostics.push(createSectionWarning(model.path, "Actions", `duplicate action id "${id}"`));
}
actionIds.add(id);
}
const target = action.target?.trim();
const isScreenEvent = action.kind?.trim() === "screen_event";
if (!target && isScreenEvent) {
diagnostics.push(createSectionInfo(model.path, "Actions", "action target is empty for screen_event"));
} else if (target && !fieldIds.has(target)) {
diagnostics.push(createSectionWarning(model.path, "Actions", `action target "${target}" does not match any Fields.id`));
}
const actionSignature = buildScreenActionDuplicateSignature(action);
if (actionSignature) {
if (actionSignatures.has(actionSignature)) {
diagnostics.push({
code: "invalid-structure",
message: `duplicate action definition "${target ?? ""}" + "${action.event?.trim() ?? ""}"`,
severity: "warning",
path: model.path,
field: "Actions",
context: { section: "Actions" }
});
}
actionSignatures.add(actionSignature);
}
const localProcessTarget = resolveScreenLocalProcessTarget(action.invoke, model);
if (localProcessTarget.kind === "resolved") {
} else if (localProcessTarget.kind === "unresolved-local") {
diagnostics.push(
createSectionWarning(
model.path,
"Actions",
`unresolved local process invoke reference "${action.invoke?.trim() ?? ""}"`
)
);
} else {
diagnostics.push(
...buildReferenceWarnings(
model.path,
"Actions",
action.invoke,
index,
"unresolved action invoke reference",
"app-process"
)
);
}
const transition = action.transition?.trim();
if (transition) {
hasTransitionAction = true;
if (!action.label?.trim()) {
diagnostics.push(
createSectionInfo(
model.path,
"Actions",
"transition preview label uses fallback because action label is empty"
)
);
}
const resolvedTransition = resolveReferenceIdentity(transition, index);
if (resolvedTransition.resolvedModel?.fileType === "screen" && resolvedTransition.resolvedModel.path === model.path) {
diagnostics.push(
createSectionWarning(
model.path,
"Actions",
`action transition "${transition}" points to the current screen`
)
);
}
}
diagnostics.push(
...buildReferenceWarnings(model.path, "Actions", action.transition, index, "unresolved action transition reference", "screen"),
...buildReferenceWarnings(model.path, "Actions", action.rule, index, "unresolved action rule reference")
);
}
for (const message of model.messages) {
diagnostics.push(
...buildReferenceWarnings(model.path, "Messages", message.text, index, "unresolved screen message reference")
);
}
if (!hasTransitionAction) {
diagnostics.push(
createSectionInfo(
model.path,
"Actions",
"no actions.transition defined for this screen"
)
);
}
return diagnostics;
}
function buildScreenActionDuplicateSignature(action) {
const target = action.target?.trim();
const event = action.event?.trim();
if (!target || !event) {
return null;
}
return [
target,
event,
action.condition?.trim() ?? "",
action.kind?.trim() ?? "",
action.invoke?.trim() ?? "",
action.transition?.trim() ?? "",
action.rule?.trim() ?? ""
].join("\0");
}
function resolveLocalHeadingTarget(value) {
const trimmed = value?.trim();
if (!trimmed) {
return null;
}
const parsed = parseReferenceValue(trimmed);
if (!parsed?.target?.startsWith("#")) {
return null;
}
const heading = parsed.target.slice(1).trim();
return heading || null;
}
function resolveScreenLocalProcessTarget(value, model) {
const trimmed = value?.trim();
if (!trimmed) {
return { kind: "not-local" };
}
const localHeadingTarget = resolveLocalHeadingTarget(trimmed);
if (localHeadingTarget) {
const exists = model.localProcesses.some(
(process) => normalizeLocalProcessId(process.id) === normalizeLocalProcessId(localHeadingTarget)
);
return exists ? { kind: "resolved", processId: localHeadingTarget } : { kind: "unresolved-local", processId: localHeadingTarget };
}
const plainId = normalizeLocalProcessId(trimmed);
if (!plainId) {
return { kind: "not-local" };
}
const plainExists = model.localProcesses.some(
(process) => normalizeLocalProcessId(process.id) === plainId
);
if (plainExists) {
return { kind: "resolved", processId: trimmed };
}
const looksLocalProcessId = /^PROC[-_A-Z0-9]+$/i.test(trimmed);
if (looksLocalProcessId) {
return { kind: "unresolved-local", processId: trimmed };
}
return { kind: "not-local" };
}
function normalizeLocalProcessId(value) {
return value?.trim().replace(/^#+/, "").trim().toUpperCase() ?? "";
}
function buildReferenceWarnings(path2, section, ref, index, messagePrefix, expectedFileType, options = {}) {
const value = ref?.trim();
if (!value) {
return [];
}
const candidates = extractModelReferenceCandidates(value);
if (candidates.length === 0) {
return [];
}
if (candidates.length > 1 || candidates[0] !== value) {
return candidates.flatMap(
(candidate) => buildSingleReferenceWarnings(
path2,
section,
candidate,
index,
messagePrefix,
expectedFileType,
options
)
);
}
return buildSingleReferenceWarnings(
path2,
section,
value,
index,
messagePrefix,
expectedFileType,
options
);
}
function buildSingleReferenceWarnings(path2, section, value, index, messagePrefix, expectedFileType, options = {}) {
const qualified = parseQualifiedRef(value);
if (qualified?.hasMemberRef) {
const resolved2 = resolveQualifiedMemberReference(value, index);
if (!resolved2.baseIdentity.resolvedModel) {
return [createSectionWarning(path2, section, formatReferenceWarningMessage(messagePrefix, value, options))];
}
if (expectedFileType && resolved2.baseIdentity.resolvedModel.fileType !== expectedFileType) {
return [createSectionWarning(path2, section, formatReferenceWarningMessage(messagePrefix, value, options))];
}
if (resolved2.memberResolution === "deferred") {
return [];
}
if (!resolved2.member) {
return [
createSectionWarning(
path2,
section,
`unresolved member ref: ${qualified.memberRef} in ${resolved2.baseIdentity.resolvedId ?? qualified.baseRefRaw}`
)
];
}
return [];
}
const parsed = parseReferenceValue(value);
if (parsed?.isExternal || parsed?.kind === "raw") {
return [];
}
const resolved = resolveReferenceIdentity(value, index);
if (!resolved.resolvedModel) {
return [createSectionWarning(path2, section, formatReferenceWarningMessage(messagePrefix, value, options))];
}
if (expectedFileType && resolved.resolvedModel.fileType !== expectedFileType) {
return [createSectionWarning(path2, section, formatReferenceWarningMessage(messagePrefix, value, options))];
}
return [];
}
function formatReferenceWarningMessage(messagePrefix, value, options) {
return options.useCouldNotResolveMessage ? `${messagePrefix} "${value}" could not be resolved. Check the ID or file name.` : `${messagePrefix} "${value}"`;
}
function createSectionWarning(path2, section, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field: section,
context: { section }
};
}
function createSectionInfo(path2, section, message) {
return {
code: "invalid-structure",
message,
severity: "info",
path: path2,
field: section,
context: { section }
};
}
function createSectionError(path2, section, message) {
return {
code: "invalid-structure",
message,
severity: "error",
path: path2,
field: section,
context: { section }
};
}
function buildDfdObjectDiagnostics(model) {
const diagnostics = [];
if (!model.id) {
diagnostics.push({
code: "invalid-structure",
message: 'required frontmatter "id" is missing',
severity: "error",
path: model.path,
field: "id",
context: {
section: "frontmatter"
}
});
}
return diagnostics;
}
function buildDataObjectDiagnostics(model, index) {
const diagnostics = [];
const fieldNameOccurrences = /* @__PURE__ */ new Map();
const fieldNumbersByRecordType = /* @__PURE__ */ new Map();
const fieldPositionsByRecordType = /* @__PURE__ */ new Map();
const recordTypes = /* @__PURE__ */ new Set();
if (!model.dataFormat?.trim()) {
diagnostics.push(createSectionWarning(model.path, "data_format", "data_format is empty"));
}
if (!model.kind?.trim()) {
diagnostics.push(createSectionWarning(model.path, "kind", "kind is empty"));
}
if (model.dataFormat?.trim() === "fixed" && !model.recordLength?.trim()) {
diagnostics.push(createSectionError(model.path, "record_length", "record_length is required when data_format is fixed"));
}
if (["csv", "tsv", "delimited"].includes(model.dataFormat?.trim() ?? "") && !model.delimiter?.trim()) {
diagnostics.push(createSectionWarning(model.path, "delimiter", "delimiter is empty for delimited data_format"));
}
for (const record of model.records) {
const recordType = record.recordType?.trim();
if (!recordType) {
continue;
}
if (recordTypes.has(recordType)) {
diagnostics.push(createSectionError(model.path, "Records", `duplicate record_type "${recordType}"`));
}
recordTypes.add(recordType);
}
for (const field of model.fields) {
const fieldName = field.name?.trim();
if (!fieldName) {
diagnostics.push({
code: "invalid-structure",
message: "field name is empty",
severity: "error",
path: model.path,
field: "Fields",
line: field.rowLine,
context: {
section: "Fields"
}
});
continue;
}
if (!field.label?.trim()) {
diagnostics.push(createFieldWarning(model.path, field.rowLine, `label is empty for field "${fieldName}"`));
}
if (!field.type?.trim()) {
diagnostics.push(createFieldWarning(model.path, field.rowLine, `type is empty for field "${fieldName}"`));
}
if (field.required?.trim() && !["Y", "N"].includes(field.required.trim())) {
diagnostics.push(createFieldWarning(model.path, field.rowLine, `required must be Y or N for field "${fieldName}"`));
}
if (field.length?.trim() && !/^\d+$/.test(field.length.trim())) {
diagnostics.push(createFieldWarning(model.path, field.rowLine, `length is not numeric for field "${fieldName}"`));
}
fieldNameOccurrences.set(fieldName, (fieldNameOccurrences.get(fieldName) ?? 0) + 1);
if (model.fieldMode === "file_layout") {
const recordType = field.recordType?.trim();
if (model.records.length > 0 && recordType && !recordTypes.has(recordType)) {
diagnostics.push(createFieldError(model.path, field.rowLine, `record_type "${recordType}" is not defined in Records`));
}
if (model.dataFormat?.trim() === "fixed" && !field.position?.trim()) {
diagnostics.push(createFieldError(model.path, field.rowLine, `position is required for fixed format field "${fieldName}"`));
}
const noKey = recordType || "__default__";
if (field.no?.trim()) {
if (!fieldNumbersByRecordType.has(noKey)) {
fieldNumbersByRecordType.set(noKey, /* @__PURE__ */ new Set());
}
const numbers = fieldNumbersByRecordType.get(noKey);
if (numbers.has(field.no.trim())) {
diagnostics.push(createFieldWarning(model.path, field.rowLine, `duplicate no "${field.no.trim()}" in record_type "${recordType || "(default)"}"`));
}
numbers.add(field.no.trim());
}
if (field.position?.trim()) {
if (!fieldPositionsByRecordType.has(noKey)) {
fieldPositionsByRecordType.set(noKey, /* @__PURE__ */ new Set());
}
const positions = fieldPositionsByRecordType.get(noKey);
if (positions.has(field.position.trim())) {
diagnostics.push(createFieldWarning(model.path, field.rowLine, `duplicate position "${field.position.trim()}" in record_type "${recordType || "(default)"}"`));
}
positions.add(field.position.trim());
}
}
const ref = field.ref?.trim();
if (!ref) {
continue;
}
const qualified = parseQualifiedRef(ref);
if (qualified?.hasMemberRef) {
const resolved2 = resolveQualifiedMemberReference(ref, index);
if (!resolved2.baseIdentity.resolvedModel) {
diagnostics.push({
code: "unresolved-reference",
message: `unresolved field reference "${ref}"`,
severity: "warning",
path: model.path,
field: "Fields",
line: field.rowLine,
context: {
section: "Fields"
}
});
continue;
}
if (resolved2.memberResolution === "deferred") {
continue;
}
if (!resolved2.member) {
diagnostics.push({
code: "unresolved-reference",
message: `unresolved member ref: ${qualified.memberRef} in ${resolved2.baseIdentity.resolvedId ?? resolved2.baseIdentity.resolvedFile ?? qualified.baseRefRaw}`,
severity: "warning",
path: model.path,
field: "Fields",
line: field.rowLine,
context: {
section: "Fields"
}
});
}
continue;
}
const parsed = parseReferenceValue(ref);
if (parsed?.isExternal || parsed?.kind === "raw") {
continue;
}
const resolved = resolveReferenceIdentity(ref, index);
if (resolved.resolvedModel) {
continue;
}
diagnostics.push({
code: "unresolved-reference",
message: `unresolved field reference "${ref}"`,
severity: "warning",
path: model.path,
field: "Fields",
line: field.rowLine,
context: {
section: "Fields"
}
});
}
for (const [fieldName, count] of fieldNameOccurrences.entries()) {
if (count > 1) {
diagnostics.push(createSectionWarning(model.path, "Fields", `duplicate field name "${fieldName}"`));
}
}
return diagnostics;
}
function createFieldWarning(path2, line, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field: "Fields",
line,
context: { section: "Fields" }
};
}
function createFieldError(path2, line, message) {
return {
code: "invalid-structure",
message,
severity: "error",
path: path2,
field: "Fields",
line,
context: { section: "Fields" }
};
}
function buildCurrentDiagramDiagnostics(diagram, warnings) {
const diagnostics = warnings.map(
(warning) => normalizeDiagnosticSeverity(attachDiagnosticModelContext(warning, diagram.diagram.fileType))
);
const missingIdDiagnostic = createMissingFrontmatterIdDiagnostic(diagram.diagram, diagnostics);
if (missingIdDiagnostic) {
diagnostics.push(missingIdDiagnostic);
}
diagnostics.push(...buildCommonSectionDiagnostics(diagram.diagram));
return finalizeCurrentDiagnostics(addModelContextToDiagnostics(diagnostics, diagram.diagram));
}
function buildClassDiagnostics(model, index) {
const diagnostics = [];
for (const relation of model.relations) {
const targetObject = resolveObjectModelReference(relation.targetClass, index);
const targetIdentity = targetObject ? void 0 : resolveReferenceIdentity(relation.targetClass, index);
if (!targetObject && !targetIdentity?.resolvedModel) {
diagnostics.push({
code: "unresolved-reference",
message: `unresolved class relation target "${relation.targetClass}"`,
severity: "warning",
path: model.path,
field: "Relations",
context: {
relatedId: relation.id,
section: "Relations"
}
});
} else if (!targetObject && targetIdentity?.resolvedModel) {
diagnostics.push({
code: "class-relation-target-not-diagram-compatible",
message: formatClassRelationTargetNotDiagramCompatibleMessage(
getReferenceDiagnosticLabel(relation.targetClass, targetIdentity)
),
severity: "warning",
path: model.path,
field: "Relations",
context: {
relatedId: relation.id,
section: "Relations"
}
});
}
if (!CLASS_RELATION_KINDS.has(relation.kind)) {
diagnostics.push({
code: "invalid-kind",
message: `invalid class relation kind "${relation.kind}"`,
severity: "warning",
path: model.path,
field: "Relations",
context: {
relatedId: relation.id,
section: "Relations"
}
});
}
}
return diagnostics;
}
function getReferenceDiagnosticLabel(reference, identity) {
return identity?.resolvedId ?? identity?.target ?? parseReferenceValue(reference)?.target ?? reference.trim();
}
function formatClassRelationTargetNotDiagramCompatibleMessage(target) {
return `class relation target "${target}" exists, but is not compatible with Class Diagram rendering and was excluded. Consider representing non-structural cross-model relationships with Mapping.`;
}
function buildErEntityDiagnostics(entity, index) {
const diagnostics = [];
const localColumnNames = new Set(entity.columns.map((column) => column.physicalName));
const relationIds = /* @__PURE__ */ new Set();
if (entity.relationBlocks.length === 0) {
diagnostics.push({
code: "section-missing",
message: 'No relations are defined in "## Relations".',
severity: "info",
path: entity.path,
field: "Relations",
context: {
section: "Relations"
}
});
}
for (const relationBlock of entity.relationBlocks) {
const relationId = relationBlock.id?.trim() ?? "";
if (!relationId) {
diagnostics.push(createSectionError(entity.path, "Relations", "invalid ER relation id: (empty)"));
} else {
if (isIncompleteErRelationId(relationId)) {
diagnostics.push(createSectionError(entity.path, "Relations", `ER relation id looks incomplete: ${relationId}`));
}
if (relationIds.has(relationId)) {
diagnostics.push(createSectionError(entity.path, "Relations", `duplicate ER relation id: ${relationId}`));
} else {
relationIds.add(relationId);
}
}
if (!relationBlock.cardinality) {
diagnostics.push({
code: "section-missing",
message: `relation "${relationBlock.id}" does not specify cardinality`,
severity: "info",
path: entity.path,
field: "Relations",
context: {
relatedId: relationBlock.id,
section: "Relations"
}
});
}
if (!relationBlock.targetTable) {
diagnostics.push({
code: "unresolved-reference",
message: `relation "${relationBlock.id}" does not resolve target_table`,
severity: "warning",
path: entity.path,
field: "Relations",
context: {
relatedId: relationBlock.id,
section: "Relations"
}
});
continue;
}
const targetEntity = resolveErEntityReference(relationBlock.targetTable, index);
if (!targetEntity) {
diagnostics.push({
code: "unresolved-reference",
message: `relation "${relationBlock.id}" target_table "${relationBlock.targetTable}" could not be resolved`,
severity: "warning",
path: entity.path,
field: "Relations",
context: {
relatedId: relationBlock.id,
section: "Relations"
}
});
continue;
}
const targetColumnNames = new Set(
targetEntity.columns.map((column) => column.physicalName)
);
for (const mapping of relationBlock.mappings) {
if (mapping.localColumn && !localColumnNames.has(mapping.localColumn)) {
diagnostics.push({
code: "unresolved-reference",
message: `relation "${relationBlock.id}" local column "${mapping.localColumn}" does not exist in the current entity`,
severity: "warning",
path: entity.path,
field: "Relations",
context: {
relatedId: relationBlock.id,
section: "Relations"
}
});
}
if (mapping.targetColumn && !targetColumnNames.has(mapping.targetColumn)) {
diagnostics.push({
code: "unresolved-reference",
message: `relation "${relationBlock.id}" target column "${mapping.targetColumn}" does not exist in "${targetEntity.physicalName}"`,
severity: "warning",
path: entity.path,
field: "Relations",
context: {
relatedId: relationBlock.id,
section: "Relations"
}
});
}
}
}
return diagnostics;
}
function isIncompleteErRelationId(id) {
const normalized = id.trim().toUpperCase();
return !normalized || normalized === "REL" || normalized === "REL-" || normalized === "REL--" || normalized === "REL-NEW" || normalized === "REL-TODO";
}
function normalizeDiagnosticSeverity(warning) {
if (warning.severity === "info" || warning.severity === "error") {
return warning;
}
if (warning.code === "frontmatter-parse-error" || warning.code === "unknown-schema" || warning.code === "invalid-table-row" || warning.code === "missing-name" || warning.code === "missing-kind") {
return { ...warning, severity: "error" };
}
if (warning.code === "invalid-table-column") {
const guidance = resolveDiagnosticSectionGuidance(warning);
return guidance?.supported === false ? warning : { ...warning, severity: "error" };
}
if (warning.code === "invalid-structure" && typeof warning.field === "string" && ["type", "id", "name", "logical_name", "physical_name", "kind"].includes(warning.field)) {
return { ...warning, severity: "error" };
}
return warning;
}
function localizeDiagnosticMessage(message, language) {
if (!isJapaneseLanguage(language)) {
return message;
}
const replacements = [
[/^Mermaid overview: no outbound relations to display\.$/, "Mermaid\u6982\u8981: \u8868\u793A\u3059\u308B\u5916\u5411\u304D\u306E\u95A2\u4FC2\u306F\u3042\u308A\u307E\u305B\u3093\u3002"],
[/^kind is empty$/, "kind \u304C\u7A7A\u3067\u3059\u3002"],
[/^summary is empty$/, "Summary \u304C\u7A7A\u3067\u3059\u3002"],
[/^values are empty$/, "Values \u304C\u7A7A\u3067\u3059\u3002"],
[/^messages are empty$/, "Messages \u304C\u7A7A\u3067\u3059\u3002"],
[/^inputs are empty$/, "Inputs \u304C\u7A7A\u3067\u3059\u3002"],
[/^conditions are empty$/, "Conditions \u304C\u7A7A\u3067\u3059\u3002"],
[/^target_ref is empty$/, "target_ref \u304C\u7A7A\u3067\u3059\u3002"],
[/^source_ref is empty and transform is also empty$/, "source_ref \u3068 transform \u306E\u4E21\u65B9\u304C\u7A7A\u3067\u3059\u3002\u3069\u3061\u3089\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"],
[/^field id is empty$/, "Fields \u306E id \u304C\u7A7A\u3067\u3059\u3002"],
[/^field name is empty$/, "Fields \u306E name \u304C\u7A7A\u3067\u3059\u3002"],
[/^values\.code is empty$/, "Values \u306E code \u304C\u7A7A\u3067\u3059\u3002"],
[/^messages\.message_id is empty$/, "Messages \u306E message_id \u304C\u7A7A\u3067\u3059\u3002"],
[/^data_format is empty$/, "data_format \u304C\u7A7A\u3067\u3059\u3002"],
[/^delimiter is empty for delimited data_format$/, "delimited \u7CFB\u306E data_format \u3067\u306F delimiter \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"],
[/^record_length is required when data_format is fixed$/, "data_format \u304C fixed \u306E\u5834\u5408\u3001record_length \u304C\u5FC5\u8981\u3067\u3059\u3002"],
[/^required frontmatter "([^"]+)" is missing$/, (_match, field) => `frontmatter \u306E "${field}" \u304C\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^frontmatter "id" is missing; id is used as the stable model identifier\.$/, 'frontmatter \u306E "id" \u304C\u3042\u308A\u307E\u305B\u3093\u3002id \u306F\u30E2\u30C7\u30EB\u306E\u5B89\u5B9A\u3057\u305F\u8B58\u5225\u5B50\u3068\u3057\u3066\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002'],
[/^expected type "([^"]+)"$/, (_match, type) => `type \u306F "${type}" \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`],
[/^unresolved DFD flow source ""$/, "DFD flow \u306E source \u304C\u672A\u6307\u5B9A\u3067\u3059\u3002"],
[/^unresolved DFD flow target ""$/, "DFD flow \u306E target \u304C\u672A\u6307\u5B9A\u3067\u3059\u3002"],
[/^unresolved DFD flow source "([^"]+)"$/, (_match, source) => `DFD flow \u306E source "${source}" \u304C\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002`],
[/^unresolved DFD flow target "([^"]+)"$/, (_match, target) => `DFD flow \u306E target "${target}" \u304C\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002`],
[/^unresolved DFD object ref "([^"]+)"$/, (_match, ref) => `DFD\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306E\u53C2\u7167 "${ref}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002ID\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^DFD flow data reference "([^"]+)" could not be resolved\. Check the data\/model id or file name\.$/, (_match, ref) => `DFD flow data reference "${ref}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002data/model \u306E id \u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^Flow Diagram flow data reference "([^"]+)" could not be resolved\. Check the data\/model id or file name\.$/, (_match, ref) => `Flow Diagram flow data reference "${ref}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002data/model \u306E id \u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^DFD object ref "([^"]+)" could not be resolved\. Check the ID or file name\.$/, (_match, ref) => `DFD\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306E\u53C2\u7167 "${ref}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002ID\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^frontmatter parse error: unexpected list item "([^"]+)"$/, (_match, value) => `frontmatter \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u4E88\u671F\u3057\u306A\u3044\u30EA\u30B9\u30C8\u9805\u76EE\u3067\u3059: "${value}"`],
[/^frontmatter parse error: malformed line "([^"]+)"$/, (_match, value) => `frontmatter \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u884C\u306E\u5F62\u5F0F\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044: "${value}"`],
[/^table in section "([^"]+)" is incomplete$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u304C\u672A\u5B8C\u6210\u3067\u3059\u3002\u30D8\u30C3\u30C0\u30FC\u884C\u3068\u533A\u5207\u308A\u884C\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^table columns in section "([^"]+)" do not match expected screen field headers$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u5217\u304C\u671F\u5F85\u3055\u308C\u308B screen field \u30D8\u30C3\u30C0\u30FC\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002`],
[/^table columns in section "([^"]+)" do not match expected legacy headers$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u5217\u304C\u671F\u5F85\u3055\u308C\u308B legacy \u30D8\u30C3\u30C0\u30FC\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002`],
[/^table columns in section "([^"]+)" do not match supported DFD object headers$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u5217\u304C\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u308B DFD object \u30D8\u30C3\u30C0\u30FC\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002`],
[/^table columns in section "([^"]+)" do not match supported class relation headers$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u5217\u304C\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u308B class relation \u30D8\u30C3\u30C0\u30FC\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002`],
[/^table columns in section "([^"]+)" do not match supported app_process Domain Sources headers$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u5217\u304C\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u308B app_process Domain Sources \u30D8\u30C3\u30C0\u30FC\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002`],
[/^table columns in section "([^"]+)" do not match expected headers$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u5217\u304C\u671F\u5F85\u3055\u308C\u308B\u30D8\u30C3\u30C0\u30FC\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002`],
[/^table row in section "([^"]+)" has (\d+) columns, expected (\d+)$/, (_match, section, actual, expected) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u884C\u306E\u5217\u6570\u304C ${actual} \u3067\u3059\u3002\u671F\u5F85\u5024\u306F ${expected} \u3067\u3059\u3002`],
[/^table row in section "([^"]+)" is missing required values$/, (_match, section) => `"${section}" \u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u30C6\u30FC\u30D6\u30EB\u884C\u306B\u5FC5\u9808\u5024\u304C\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^Format table should use: key \| value \| notes$/, "Format \u30C6\u30FC\u30D6\u30EB\u306F key | value | notes \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002"],
[/^Records table should use: record_type \| name \| occurrence \| notes$/, "Records \u30C6\u30FC\u30D6\u30EB\u306F record_type | name | occurrence | notes \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002"],
[/^Fields table mixes standard and file layout columns; parsed as file_layout$/, "Fields \u30C6\u30FC\u30D6\u30EB\u306B standard \u5F62\u5F0F\u3068 file_layout \u5F62\u5F0F\u306E\u5217\u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002file_layout \u3068\u3057\u3066\u89E3\u6790\u3057\u307E\u3057\u305F\u3002"],
[/^duplicate field name "([^"]+)"$/, (_match, name) => `\u30D5\u30A3\u30FC\u30EB\u30C9\u540D "${name}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate field id "([^"]+)"$/, (_match, id) => `Fields.id "${id}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate mapping row "([^"]+)"$/, (_match, value) => `mapping row "${value}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^mapping target member "([^"]+)" is mapped from multiple sources\.$/, (_match, value) => `mapping target member "${value}" \u304C\u8907\u6570\u306E source_ref \u304B\u3089\u5BFE\u5FDC\u4ED8\u3051\u3089\u308C\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate (ER relation id): (.+)$/, (_match, target, value) => `${target}: ${value} \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate id detected: "([^"]+)"$/, (_match, id) => `id "${id}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate model id detected: "([^"]+)" in (.+)$/, (_match, id, paths) => `model id "${id}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${paths}`],
[/^filename and id mismatch: "([^"]+)" != "([^"]+)"$/, (_match, filename, id) => `\u30D5\u30A1\u30A4\u30EB\u540D\u3068 id \u304C\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093: "${filename}" != "${id}"`],
[/^label is empty for (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u306E label \u304C\u7A7A\u3067\u3059\u3002`],
[/^type is empty for field "([^"]+)"$/, (_match, field) => `field "${field}" \u306E type \u304C\u7A7A\u3067\u3059\u3002`],
[/^required must be Y or N for (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u306E required \u306F Y \u307E\u305F\u306F N \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^active must be Y or N for (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u306E active \u306F Y \u307E\u305F\u306F N \u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^active is empty for (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u306E active \u304C\u7A7A\u3067\u3059\u3002`],
[/^severity is empty for message_id "([^"]+)"$/, (_match, id) => `message_id "${id}" \u306E severity \u304C\u7A7A\u3067\u3059\u3002`],
[/^severity is invalid for message_id "([^"]+)"$/, (_match, id) => `message_id "${id}" \u306E severity \u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^timing is empty for message_id "([^"]+)"$/, (_match, id) => `message_id "${id}" \u306E timing \u304C\u7A7A\u3067\u3059\u3002`],
[/^audience is empty for message_id "([^"]+)"$/, (_match, id) => `message_id "${id}" \u306E audience \u304C\u7A7A\u3067\u3059\u3002`],
[/^text is empty for message_id "([^"]+)"$/, (_match, id) => `message_id "${id}" \u306E text \u304C\u7A7A\u3067\u3059\u3002`],
[/^notes are empty for (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u306E notes \u304C\u7A7A\u3067\u3059\u3002`],
[/^sort_order is empty for code "([^"]+)"$/, (_match, code) => `code "${code}" \u306E sort_order \u304C\u7A7A\u3067\u3059\u3002`],
[/^sort_order is not numeric for code "([^"]+)"$/, (_match, code) => `code "${code}" \u306E sort_order \u304C\u6570\u5024\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^inactive (code|message) "([^"]+)" is defined$/, (_match, target, value) => `${target} "${value}" \u306F inactive \u3068\u3057\u3066\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u3059\u3002`],
[/^length is not numeric for field "([^"]+)"$/, (_match, field) => `field "${field}" \u306E length \u304C\u6570\u5024\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^position is required for fixed format field "([^"]+)"$/, (_match, field) => `fixed \u5F62\u5F0F\u306E field "${field}" \u306B\u306F position \u304C\u5FC5\u8981\u3067\u3059\u3002`],
[/^record_type "([^"]+)" is not defined in Records$/, (_match, recordType) => `record_type "${recordType}" \u304C Records \u306B\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^duplicate no "([^"]+)" in record_type "([^"]+)"$/, (_match, no, recordType) => `record_type "${recordType}" \u5185\u3067 no "${no}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate position "([^"]+)" in record_type "([^"]+)"$/, (_match, position, recordType) => `record_type "${recordType}" \u5185\u3067 position "${position}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^field layout "([^"]+)" does not match any Layout\.id$/, (_match, layout) => `field layout "${layout}" \u306B\u4E00\u81F4\u3059\u308B Layout.id \u304C\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^layout is empty for field "([^"]+)"$/, (_match, field) => `field "${field}" \u306E layout \u304C\u7A7A\u3067\u3059\u3002`],
[/^action target is empty for screen_event$/, "screen_event \u306E action target \u304C\u7A7A\u3067\u3059\u3002"],
[/^action target "([^"]+)" does not match any Fields\.id$/, (_match, target) => `action target "${target}" \u306B\u4E00\u81F4\u3059\u308B Fields.id \u304C\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^duplicate action definition "([^"]+)" \+ "([^"]+)"$/, (_match, target, event) => `action \u5B9A\u7FA9 "${target}" + "${event}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^duplicate action target\/event pair "([^"]+)" \+ "([^"]+)"$/, (_match, target, event) => `action target/event \u306E\u7D44\u307F\u5408\u308F\u305B "${target}" + "${event}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^transition preview label uses fallback because action label is empty$/, "action label \u304C\u7A7A\u306E\u305F\u3081\u3001transition \u30D7\u30EC\u30D3\u30E5\u30FC\u3067\u306F\u4EE3\u66FF\u30E9\u30D9\u30EB\u3092\u4F7F\u3044\u307E\u3059\u3002"],
[/^action transition "([^"]+)" points to the current screen$/, (_match, transition) => `action transition "${transition}" \u304C\u73FE\u5728\u306E screen \u3092\u6307\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^no actions\.transition defined for this screen$/, "\u3053\u306E screen \u306B\u306F actions.transition \u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002"],
[/^legacy "Transitions" section detected; migrate to Actions\.transition$/, '\u65E7\u5F62\u5F0F\u306E "Transitions" \u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u3042\u308A\u307E\u3059\u3002Actions.transition \u3078\u306E\u79FB\u884C\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002'],
[/^app_process Flow\.(from|to) references missing step "([^"]+)"$/, (_match, endpoint, step) => `app_process Flow.${endpoint} \u304C\u5B58\u5728\u3057\u306A\u3044 step "${step}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^app_process Flow\.(from|to) is missing a step id$/, (_match, endpoint) => `app_process Flow.${endpoint} \u306E step id \u304C\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^Step "([^"]+)" has both domain and lane\. domain is used and lane is ignored\.$/, (_match, step) => `Step "${step}" \u306B\u306F domain \u3068 lane \u306E\u4E21\u65B9\u304C\u3042\u308A\u307E\u3059\u3002domain \u304C\u4F7F\u308F\u308C\u3001lane \u306F\u7121\u8996\u3055\u308C\u307E\u3059\u3002`],
[/^app_process Step "([^"]+)" references unknown Domain "([^"]+)"\.$/, (_match, step, domain) => `app_process Step "${step}" \u304C\u672A\u5B9A\u7FA9\u306E Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^app_process Step "([^"]+)" references unknown local Domain "([^"]+)"\.$/, (_match, step, domain) => `app_process Step "${step}" \u304C\u672A\u5B9A\u7FA9\u306E\u30ED\u30FC\u30AB\u30EB Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^app_process local Domain "([^"]+)" overrides external Domain (name|kind|parent)\.$/, (_match, domain, field) => `app_process \u30ED\u30FC\u30AB\u30EB Domain "${domain}" \u304C\u5916\u90E8 Domain \u306E ${field} \u3092\u4E0A\u66F8\u304D\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^(.+) "([^"]+)" could not be resolved\. Check the ID or file name\.$/, (_match, target, value) => `${target} "${value}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002ID\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^unresolved (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002ID\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^unresolved member ref: (.+) in (.+)$/, (_match, member, owner) => `member ref "${member}" \u304C "${owner}" \u5185\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`],
[/^relation "([^"]+)" target_table "([^"]+)" could not be resolved$/, (_match, relation, target) => `relation "${relation}" \u306E target_table "${target}" \u304C\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002`],
[/^relation "([^"]+)" does not resolve target_table$/, (_match, relation) => `relation "${relation}" \u306E target_table \u304C\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002`],
[/^relation "([^"]+)" does not specify cardinality$/, (_match, relation) => `relation "${relation}" \u306B cardinality \u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^relation "([^"]+)" local column "([^"]+)" does not exist in the current entity$/, (_match, relation, column) => `relation "${relation}" \u306E local column "${column}" \u306F\u73FE\u5728\u306E entity \u306B\u5B58\u5728\u3057\u307E\u305B\u3093\u3002`],
[/^relation "([^"]+)" target column "([^"]+)" does not exist in "([^"]+)"$/, (_match, relation, column, entity) => `relation "${relation}" \u306E target column "${column}" \u306F "${entity}" \u306B\u5B58\u5728\u3057\u307E\u305B\u3093\u3002`],
[/^No relations are defined in "## Relations"\.$/, "## Relations \u306B relation \u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002"],
[/^invalid ER relation id: \(empty\)$/, "ER relation id \u304C\u7A7A\u3067\u3059\u3002"],
[/^ER relation id looks incomplete: (.+)$/, (_match, id) => `ER relation id \u304C\u672A\u5B8C\u6210\u306E\u3088\u3046\u3067\u3059: ${id}`],
[/^class relation target "([^"]+)" exists, but is not compatible with Class Diagram rendering and was excluded\. Consider representing non-structural cross-model relationships with Mapping\.$/, (_match, target) => `class relation target "${target}" \u306F\u5B58\u5728\u3057\u307E\u3059\u304C\u3001Class Diagram \u306E\u63CF\u753B\u5BFE\u8C61\u3067\u306F\u306A\u3044\u305F\u3081\u9664\u5916\u3055\u308C\u307E\u3057\u305F\u3002\u30AF\u30E9\u30B9\u56F3\u306E\u69CB\u9020\u95A2\u4FC2\u3067\u306F\u306A\u3044\u5BFE\u5FDC\u306F Mapping \u3067\u306E\u8868\u73FE\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^invalid class relation kind "([^"]+)"$/, (_match, kind) => `class relation kind "${kind}" \u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^reserved kind used: "([^"]+)"$/, (_match, kind) => `\u4E88\u7D04\u6E08\u307F kind "${kind}" \u304C\u4F7F\u308F\u308C\u3066\u3044\u307E\u3059\u3002`],
[/^(.+) renderer is not supported for (.+)\. Using the format default renderer\.$/, (_match, renderer, format) => `${format} \u3067\u306F ${renderer} renderer \u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002format \u306E\u65E2\u5B9A renderer \u3092\u4F7F\u3044\u307E\u3059\u3002`],
[/^Unknown render_mode value "([^"]+)"\. Using the format default renderer\.$/, (_match, value) => `render_mode "${value}" \u306F\u4E0D\u660E\u3067\u3059\u3002format \u306E\u65E2\u5B9A renderer \u3092\u4F7F\u3044\u307E\u3059\u3002`],
[/^unknown flow_view; expected "detail" or "screen"$/, 'flow_view \u306F "detail" \u307E\u305F\u306F "screen" \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002'],
[/^DFD flow shape "([^"]+)" may be unusual$/, (_match, shape) => `DFD flow shape "${shape}" \u306F\u901A\u5E38\u3068\u7570\u306A\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`],
[/^DFD flow "([^"]+)" is a self-loop$/, (_match, flow) => `DFD flow "${flow}" \u306F\u81EA\u5DF1\u30EB\u30FC\u30D7\u3067\u3059\u3002`],
[/^Domain id is required\.$/, "Domain \u306E id \u304C\u5FC5\u8981\u3067\u3059\u3002"],
[/^duplicate Domain id "([^"]+)"$/, (_match, id) => `Domain id "${id}" \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^Domain parent "([^"]+)" is not defined\.$/, (_match, parent) => `Domain parent "${parent}" \u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^Domain "([^"]+)" cannot use itself as parent\.$/, (_match, domain) => `Domain "${domain}" \u306F\u81EA\u5206\u81EA\u8EAB\u3092 parent \u306B\u3067\u304D\u307E\u305B\u3093\u3002`],
[/^Domain parent cycle detected: (.+)$/, (_match, chain) => `Domain \u306E parent \u304C\u5FAA\u74B0\u3057\u3066\u3044\u307E\u3059: ${chain}`],
[/^Domain "([^"]+)" is defined in multiple Domains files\.$/, (_match, domain) => `Domain "${domain}" \u304C\u8907\u6570\u306E Domains \u30D5\u30A1\u30A4\u30EB\u3067\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u3059\u3002`],
[/^Domain "([^"]+)" has conflicting (name|kind|parent) values across Domains files\.$/, (_match, domain, field) => `Domain "${domain}" \u306E ${field} \u304C\u8907\u6570\u306E Domains \u30D5\u30A1\u30A4\u30EB\u3067\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^Color Scheme kind is required\.$/, "Color Scheme \u306E kind \u304C\u5FC5\u8981\u3067\u3059\u3002"],
[/^Color Scheme (fill|stroke|text) "([^"]+)" is not a supported hex color\.$/, (_match, field, value) => `Color Scheme \u306E ${field} "${value}" \u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u308B hex color \u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^duplicate Color Scheme entry for target "([^"]+)" and kind "([^"]+)"$/, (_match, target, kind) => `target "${target}" \u3068 kind "${kind}" \u306E Color Scheme entry \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^Default Color Scheme ref "([^"]+)" could not be resolved\. Built-in colors will be used\.$/, (_match, ref) => `\u65E2\u5B9A\u306E Color Scheme ref "${ref}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u7D44\u307F\u8FBC\u307F\u8272\u3092\u4F7F\u3044\u307E\u3059\u3002`],
[/^Default Color Scheme ref "([^"]+)" resolves to type "([^"]+)", but expected type "color_scheme"\. Built-in colors will be used\.$/, (_match, ref, fileType) => `\u65E2\u5B9A\u306E Color Scheme ref "${ref}" \u306F type "${fileType}" \u306B\u89E3\u6C7A\u3055\u308C\u307E\u3057\u305F\u304C\u3001type "color_scheme" \u304C\u5FC5\u8981\u3067\u3059\u3002\u7D44\u307F\u8FBC\u307F\u8272\u3092\u4F7F\u3044\u307E\u3059\u3002`],
[/^Domain Source ref is required\.$/, "Domain Source \u306E ref \u304C\u5FC5\u8981\u3067\u3059\u3002"],
[/^Domain Source ref "([^"]+)" could not be resolved\. Check the ID or file name\.$/, (_match, ref) => `Domain Source ref "${ref}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002ID\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
[/^Domain Source ref "([^"]+)" resolves to type "([^"]+)", but expected type "domains"\.$/, (_match, ref, fileType) => `Domain Source ref "${ref}" \u306F type "${fileType}" \u306B\u89E3\u6C7A\u3055\u308C\u307E\u3057\u305F\u304C\u3001type "domains" \u304C\u5FC5\u8981\u3067\u3059\u3002`],
[/^Domain Diagram has no valid Domain Sources\.$/, "Domain Diagram \u306B\u6709\u52B9\u306A Domain Sources \u304C\u3042\u308A\u307E\u305B\u3093\u3002"],
[/^Domain Source ref "([^"]+)" has no Domain rows\.$/, (_match, ref) => `Domain Source ref "${ref}" \u306B Domain \u884C\u304C\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^Domain "([^"]+)" is defined by multiple Domain Diagram sources: "([^"]+)" and "([^"]+)"\.$/, (_match, domain, earlier, later) => `Domain "${domain}" \u304C\u8907\u6570\u306E Domain Diagram source \u3067\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u3059: "${earlier}" \u3068 "${later}"\u3002`],
[/^Domain "([^"]+)" has conflicting (name|kind|parent) values between Domain Diagram sources "([^"]+)" and "([^"]+)"\.$/, (_match, domain, field, earlier, later) => `Domain "${domain}" \u306E ${field} \u304C Domain Diagram source "${earlier}" \u3068 "${later}" \u3067\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^DFD-local Domain "([^"]+)" is not defined in shared Domains\.$/, (_match, domain) => `DFD\u5185\u306E Domain "${domain}" \u306F\u5171\u901A Domains \u306B\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^DFD-local Domain "([^"]+)" has (name|kind|parent) "([^"]*)", but shared Domains define \2 "([^"]*)"\.$/, (_match, domain, field, local, shared) => `DFD\u5185\u306E Domain "${domain}" \u306E ${field} \u306F "${local}" \u3067\u3059\u304C\u3001\u5171\u901A Domains \u3067\u306F "${shared}" \u3068\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u3059\u3002`],
[/^DFD-local Domain "([^"]+)" overrides Domain Source (name|kind|parent) "([^"]*)" with "([^"]*)"\.$/, (_match, domain, field, source, local) => `DFD\u5185\u306E Domain "${domain}" \u306F Domain Source \u306E ${field} "${source}" \u3092 "${local}" \u3067\u4E0A\u66F8\u304D\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^DFD object "([^"]+)" references unknown local Domain "([^"]+)"\.$/, (_match, object, domain) => `DFD object "${object}" \u304C\u672A\u5B9A\u7FA9\u306E\u30ED\u30FC\u30AB\u30EB Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^DFD object "([^"]+)" references unknown Domain "([^"]+)"\.$/, (_match, object, domain) => `DFD object "${object}" \u304C\u672A\u5B9A\u7FA9\u306E Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^Flow Diagram local Domain "([^"]+)" overrides Domain Source (name|kind|parent) "([^"]*)" with "([^"]*)"\.$/, (_match, domain, field, source, local) => `Flow Diagram \u30ED\u30FC\u30AB\u30EB Domain "${domain}" \u306F Domain Source \u306E ${field} "${source}" \u3092 "${local}" \u3067\u4E0A\u66F8\u304D\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^Flow Diagram object "([^"]+)" references unknown local Domain "([^"]+)"\.$/, (_match, object, domain) => `Flow Diagram object "${object}" \u304C\u672A\u5B9A\u7FA9\u306E\u30ED\u30FC\u30AB\u30EB Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^Flow Diagram object "([^"]+)" references unknown Domain "([^"]+)"\.$/, (_match, object, domain) => `Flow Diagram object "${object}" \u304C\u672A\u5B9A\u7FA9\u306E Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
[/^DFD object "([^"]+)" references Domain "([^"]+)", but this DFD has no local Domains\.$/, (_match, object, domain) => `DFD object "${object}" \u304C Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u304C\u3001\u3053\u306E DFD \u306B\u306F\u30ED\u30FC\u30AB\u30EB Domains \u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^DFD local object "([^"]+)" is treated as an inline object without ref\.$/, (_match, object) => `DFD local object "${object}" \u306F ref \u306A\u3057\u306E\u56F3\u5185\u5B9A\u7FA9\u3068\u3057\u3066\u6271\u308F\u308C\u307E\u3059\u3002`],
[/^DFD object "([^"]+)" has no kind, and it could not be inferred from ref\.$/, (_match, object) => `DFD object "${object}" \u306E kind \u304C\u306A\u304F\u3001ref \u304B\u3089\u3082\u63A8\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002`],
[/^(.+) resolves to a dfd_object but is not listed in "Objects"$/, (_match, value) => `${value} \u306F dfd_object \u306B\u89E3\u6C7A\u3067\u304D\u307E\u3059\u304C\u3001Objects \u306B listed \u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^(.+) is not listed in "Objects"$/, (_match, value) => `${value} \u306F Objects \u306B listed \u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002`],
[/^Duplicate object refs were merged: (.+)$/, (_match, summary) => `\u91CD\u8907\u3057\u305F object ref \u3092\u7D71\u5408\u3057\u307E\u3057\u305F: ${summary}`],
[/^relation "([^"]+)" is outside diagram scope$/, (_match, relation) => `relation "${relation}" \u306F diagram \u306E\u5BFE\u8C61\u7BC4\u56F2\u5916\u3067\u3059\u3002`],
[/^diagram relations are empty; using auto-collected class relations from "Objects"$/, "diagram relations \u304C\u7A7A\u306E\u305F\u3081\u3001Objects \u304B\u3089\u81EA\u52D5\u53CE\u96C6\u3057\u305F class relations \u3092\u4F7F\u3044\u307E\u3059\u3002"],
[/^diagram parser expected type "([^"]+)" or "([^"]+)" but received type "([^"]+)"$/, (_match, left, right, actual) => `diagram parser \u306F type "${left}" \u307E\u305F\u306F "${right}" \u3092\u671F\u5F85\u3057\u307E\u3057\u305F\u304C\u3001\u5B9F\u969B\u306F "${actual}" \u3067\u3057\u305F\u3002`],
[/^relations parser expected schema "([^"]+)" but received "([^"]+)"$/, (_match, expected, actual) => `relations parser \u306F schema "${expected}" \u3092\u671F\u5F85\u3057\u307E\u3057\u305F\u304C\u3001\u5B9F\u969B\u306F "${actual}" \u3067\u3057\u305F\u3002`],
[/^object parser expected schema "([^"]+)" or type "([^"]+)" but received schema "([^"]+)" \/ type "([^"]+)"$/, (_match, schema, type, actualSchema, actualType) => `object parser \u306F schema "${schema}" \u307E\u305F\u306F type "${type}" \u3092\u671F\u5F85\u3057\u307E\u3057\u305F\u304C\u3001\u5B9F\u969B\u306F schema "${actualSchema}" / type "${actualType}" \u3067\u3057\u305F\u3002`],
[/^invalid kind "([^"]+)"$/, (_match, kind) => `kind "${kind}" \u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^invalid dfd_object kind "([^"]+)"$/, (_match, kind) => `dfd_object kind "${kind}" \u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^malformed relation record: missing (.+)$/, (_match, fields) => `relation record \u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002\u4E0D\u8DB3: ${fields}`],
[/^Relations row is missing required values: (.+)$/, (_match, row) => `Relations \u884C\u306B\u5FC5\u9808\u5024\u304C\u3042\u308A\u307E\u305B\u3093: ${row}`],
[/^failed to parse numeric value "([^"]+)" for "([^"]+)"$/, (_match, value, field) => `"${field}" \u306E\u6570\u5024 "${value}" \u3092\u89E3\u6790\u3067\u304D\u307E\u305B\u3093\u3002`],
[/^missing required field "([^"]+)"$/, (_match, field) => `\u5FC5\u9808\u30D5\u30A3\u30FC\u30EB\u30C9 "${field}" \u304C\u3042\u308A\u307E\u305B\u3093\u3002`],
[/^relation block "([^"]+)" missing required field "([^"]+)"$/, (_match, block, field) => `relation block "${block}" \u306B\u5FC5\u9808\u30D5\u30A3\u30FC\u30EB\u30C9 "${field}" \u304C\u3042\u308A\u307E\u305B\u3093\u3002`]
];
for (const [pattern, replacement] of replacements) {
const matched = message.match(pattern);
if (!matched) {
continue;
}
if (typeof replacement === "string") {
return replacement;
}
return replacement(...matched);
}
return message;
}
function finalizeCurrentDiagnostics(warnings) {
return dedupeDiagnostics(suppressDiagnosticsAfterInvalidSectionHeader(warnings));
}
function suppressDiagnosticsAfterInvalidSectionHeader(warnings) {
const invalidHeaderSections = /* @__PURE__ */ new Set();
for (const warning of warnings) {
if (isInvalidSectionHeaderDiagnostic(warning)) {
const key = getDiagnosticSectionKey(warning);
if (key) {
invalidHeaderSections.add(key);
}
}
}
if (invalidHeaderSections.size === 0) {
return warnings;
}
return warnings.filter((warning) => {
if (isInvalidSectionHeaderDiagnostic(warning)) {
return true;
}
const key = getDiagnosticSectionKey(warning);
if (!key || !invalidHeaderSections.has(key)) {
return true;
}
return !isLikelyCascadingRowDiagnostic(warning);
});
}
function isInvalidSectionHeaderDiagnostic(warning) {
return warning.code === "invalid-table-column" || /table columns in section/i.test(warning.message) || /table should use:/i.test(warning.message) || /do not match expected .*headers/i.test(warning.message) || /do not match supported .*headers/i.test(warning.message);
}
function isLikelyCascadingRowDiagnostic(warning) {
if (warning.code === "invalid-table-row") {
return true;
}
if (warning.code !== "invalid-structure" && warning.code !== "invalid-object-ref" && warning.code !== "unresolved-reference") {
return false;
}
return /duplicate .*(?:entry|id|key|row|target|kind)/i.test(warning.message) || /table row in section/i.test(warning.message) || /missing required values/i.test(warning.message) || /missing "(?:id|ref|from|to|source|target)"/i.test(warning.message) || /(?:id|ref|from|to|source|target|kind|name|data|message_id) is empty/i.test(warning.message) || /is missing a step id/i.test(warning.message) || /must have "id" or "ref"/i.test(warning.message) || /unresolved .+ ""/i.test(warning.message) || /has no kind, and it could not be inferred/i.test(warning.message);
}
function getDiagnosticSectionKey(warning) {
const section = getDiagnosticSectionNameForSuppression(warning);
if (!section) {
return null;
}
const path2 = warning.path ?? warning.filePath ?? "";
const fileType = typeof warning.context?.fileType === "string" ? warning.context.fileType : "";
return [path2, fileType, section.trim().toLowerCase()].join("\0");
}
function getDiagnosticSectionNameForSuppression(warning) {
const contextSection = typeof warning.context?.section === "string" ? getSectionNameFromField(warning.context.section) : "";
if (contextSection) {
return contextSection;
}
const quoted = warning.message.match(/section "([^"]+)"/i)?.[1];
if (quoted) {
return quoted;
}
return getSectionNameFromField(warning.field);
}
function getSectionNameFromField(field) {
const section = field?.split(".")[0]?.split(":")[0]?.trim();
return section || null;
}
function dedupeDiagnostics(warnings) {
return warnings.filter(
(warning, index) => warnings.findIndex(
(entry) => entry.code === warning.code && entry.message === warning.message && entry.severity === warning.severity && entry.path === warning.path && entry.field === warning.field
) === index
);
}
// src/core/impact-analyzer.ts
function buildImpactSummary(model, index) {
const outboundReferences = collectModelReferences(model).map(
(reference) => createImpactReference(model, reference, "outbound", index)
);
const resolvedOutbound = outboundReferences.filter(
(reference) => Boolean(reference.targetPath)
);
const unresolvedOutbound = outboundReferences.filter(
(reference) => !reference.targetPath && isExternalModelReference(reference.targetRaw)
);
const inboundReferences = [];
for (const candidate of Object.values(index.modelsByFilePath)) {
if (candidate.path === model.path || candidate.fileType === "markdown") {
continue;
}
for (const reference of collectModelReferences(candidate)) {
if (referenceTargetsModel(reference.raw, model, index)) {
inboundReferences.push(createImpactReference(candidate, reference, "inbound", index));
}
}
}
const relatedSourceLinks = collectRelatedSourceLinks(
model,
resolvedOutbound,
inboundReferences,
index
);
return {
modelPath: model.path,
modelId: getModelId(model),
modelType: model.fileType,
modelLabel: getReferencedModelDisplayName(model),
outboundRelationships: groupOutboundRelationships(resolvedOutbound, index),
inboundRelationships: groupInboundRelationships(inboundReferences, index),
valueUsages: model.fileType === "codeset" ? groupValueUsages(model, inboundReferences, index) : [],
unresolvedOutbound,
relatedSourceLinks
};
}
function formatImpactSummaryAsMarkdown(summary) {
const title = summary.modelId ?? summary.modelLabel;
return [
`# Relationship summary: ${title}`,
"",
`Model: ${summary.modelLabel}`,
`Type: ${summary.modelType}`,
...summary.modelId ? [`ID: ${summary.modelId}`] : [],
"",
formatCategorizedRelationshipSection("## Used by", summary.inboundRelationships),
"",
formatCategorizedRelationshipSection("## References", summary.outboundRelationships),
"",
...summary.modelType === "codeset" ? [formatValueUsageSection("## Value usage", summary.valueUsages), ""] : [],
formatUnresolvedSection("## Unresolved", summary.unresolvedOutbound),
"",
formatSourceLinkCountSection("## Source links", summary.relatedSourceLinks)
].join("\n");
}
var IMPACT_RELATIONSHIP_CATEGORY_ORDER = [
"screens",
"processes",
"rules",
"mappings",
"diagrams",
"classes",
"dataEr",
"other"
];
function getImpactRelationshipCategoryKey(relationship) {
const type = relationship.modelType.replace(/_/g, "-");
if (type === "screen") {
return "screens";
}
if (type === "app-process" || type === "process") {
return "processes";
}
if (type === "rule") {
return "rules";
}
if (type === "mapping") {
return "mappings";
}
if (["dfd-diagram", "flow-diagram", "er-diagram", "class-diagram", "domain-diagram", "diagram"].includes(type)) {
return "diagrams";
}
if (type === "class" || type === "object") {
return "classes";
}
if (type === "data-object" || type === "er-entity") {
return "dataEr";
}
const id = relationship.modelId?.toUpperCase() ?? "";
if (id.startsWith("SCR-")) {
return "screens";
}
if (id.startsWith("PROC-")) {
return "processes";
}
if (id.startsWith("RULE-")) {
return "rules";
}
if (id.startsWith("MAP-")) {
return "mappings";
}
if (id.startsWith("DFD-") || id.startsWith("ERD-") || id.startsWith("CLD-") || id.startsWith("DOMAIN-DIAGRAM-")) {
return "diagrams";
}
if (id.startsWith("CLS-")) {
return "classes";
}
if (id.startsWith("DATA-") || id.startsWith("ENT-")) {
return "dataEr";
}
return "other";
}
function collectModelReferences(model) {
const references = [];
const add = (raw, relationKind, section, field, notes, sourceContext) => {
const trimmed = raw?.trim();
if (!trimmed) {
return;
}
for (const candidate of extractModelReferenceCandidates(trimmed)) {
references.push({
raw: candidate,
relationKind,
section,
field,
sourceContext: sourceContext?.trim() || void 0,
notes: notes?.trim() || void 0
});
}
};
switch (model.fileType) {
case "object":
for (const relation of model.relations) {
add(relation.targetClass, relation.kind || "class relation", "Relations", "targetClass", relation.notes);
}
break;
case "er-entity":
for (const relation of model.outboundRelations) {
add(relation.targetEntity, relation.kind || "er relation", "Relations", "targetEntity", relation.notes);
}
break;
case "diagram":
for (const ref of model.objectRefs) {
add(ref, "diagram object", "Objects", "objectRefs");
}
for (const node of model.nodes) {
add(node.ref, "diagram node", "Nodes", "ref");
}
for (const edge of model.edges) {
add(edge.source, edge.kind || "diagram edge", "Edges", "source");
add(edge.target, edge.kind || "diagram edge", "Edges", "target");
}
break;
case "dfd-diagram":
case "flow-diagram": {
const relationPrefix = model.fileType === "flow-diagram" ? "flow diagram" : "dfd";
for (const ref of model.objectRefs) {
add(ref, `${relationPrefix} object`, "Objects", "objectRefs");
}
for (const object of model.objectEntries) {
add(object.ref, `${relationPrefix} object`, "Objects", "ref", object.notes);
}
for (const flow of model.flows) {
add(flow.from, `${relationPrefix} flow`, "Flows", "from", flow.notes);
add(flow.to, `${relationPrefix} flow`, "Flows", "to", flow.notes);
add(flow.data, `${relationPrefix} data`, "Flows", "data", flow.notes);
}
break;
}
case "domains":
break;
case "data-object":
for (const field of model.fields) {
add(field.ref, "data field reference", "Fields", "ref", field.notes);
}
break;
case "app-process":
for (const input of model.inputs) {
add(input.data, "process input", "Inputs", "data", input.notes);
add(input.source, "process input source", "Inputs", "source", input.notes);
}
for (const output of model.outputs) {
add(output.data, "process output", "Outputs", "data", output.notes);
add(output.target, "process output target", "Outputs", "target", output.notes);
}
for (const trigger of model.triggers) {
add(trigger.source, "process trigger", "Triggers", "source", trigger.notes);
}
for (const transition of model.transitions) {
add(transition.to, "process transition", "Transitions", "to", transition.notes);
}
for (const flow of model.flows ?? []) {
if (parseStructuredQualifiedReference(flow.condition)) {
add(
flow.condition,
"process flow condition",
"Flows",
"condition",
flow.notes,
[flow.from, flow.to].filter(Boolean).join(" -> ")
);
}
}
for (const step of model.steps ?? []) {
add(step.input, "process step input", "Steps", "input", step.notes);
add(step.output, "process step output", "Steps", "output", step.notes);
add(step.rule, "process step rule", "Steps", "rule", step.notes);
add(step.invoke, "process step invoke", "Steps", "invoke", step.notes);
add(step.screen, "process step screen", "Steps", "screen", step.notes);
}
break;
case "screen":
for (const field of model.fields) {
add(field.ref, "screen field reference", "Fields", "ref", field.notes);
add(field.rule, "screen field rule", "Fields", "rule", field.notes);
if (parseStructuredQualifiedReference(field.condition)) {
add(
field.condition,
"screen field condition",
"Fields",
"condition",
field.notes,
formatScreenFieldContext(field)
);
}
}
for (const action of model.actions) {
add(action.invoke, "screen action invoke", "Actions", "invoke", action.notes);
add(action.transition, "screen action transition", "Actions", "transition", action.notes);
add(action.rule, "screen action rule", "Actions", "rule", action.notes);
if (parseStructuredQualifiedReference(action.condition)) {
add(
action.condition,
"screen action condition",
"Actions",
"condition",
action.notes,
formatScreenActionContext(action)
);
}
}
for (const message of model.messages) {
if (parseStructuredQualifiedReference(message.condition)) {
add(
message.condition,
"screen message condition",
"Messages",
"condition",
message.notes,
formatScreenMessageContext(message)
);
}
}
for (const localProcess of model.localProcesses) {
for (const step of localProcess.steps ?? []) {
if (parseStructuredQualifiedReference(step.condition)) {
add(
step.condition,
"screen local process step condition",
"Local Processes",
"Steps.condition",
step.notes,
formatScreenLocalProcessRowContext(localProcess.id, step)
);
}
}
for (const error of localProcess.errors ?? []) {
if (parseStructuredQualifiedReference(error.condition)) {
add(
error.condition,
"screen local process error condition",
"Local Processes",
"Errors.condition",
error.notes,
formatScreenLocalProcessRowContext(localProcess.id, error)
);
}
}
}
for (const transition of model.legacyTransitions) {
add(transition.to, "screen transition", "Transitions", "to", transition.notes);
}
break;
case "rule":
for (const input of model.inputs) {
add(input.data, "rule input", "Inputs", "data", input.notes);
add(input.source, "rule input source", "Inputs", "source", input.notes);
}
for (const reference of model.references) {
add(reference.ref, "rule reference", "References", "ref", reference.notes);
}
for (const message of model.messages) {
add(message.message, "rule message", "Messages", "message", message.notes);
}
break;
case "mapping":
add(model.source, "mapping source", "Overview", "source");
add(model.target, "mapping target", "Overview", "target");
for (const scope of model.scope) {
add(scope.ref, "mapping scope", "Scope", "ref", scope.notes);
}
for (const row of model.mappings) {
add(row.sourceRef, "mapping source field", "Mappings", "sourceRef", row.notes);
add(row.targetRef, "mapping target field", "Mappings", "targetRef", row.notes);
add(row.rule, "mapping rule", "Mappings", "rule", row.notes);
}
break;
default:
break;
}
return references;
}
function createImpactReference(sourceModel, reference, direction, index) {
const rawIdentity = resolveReferenceIdentity(reference.raw, index);
const qualified = resolveQualifiedMemberReference(reference.raw, index);
const identity = rawIdentity.resolvedModel || !qualified.qualified.hasMemberRef ? rawIdentity : qualified.baseIdentity;
return {
direction,
sourcePath: sourceModel.path,
sourceId: getModelId(sourceModel),
sourceType: sourceModel.fileType,
sourceLabel: getReferencedModelDisplayName(sourceModel),
targetRaw: reference.raw,
targetPath: identity.resolvedFile,
targetId: identity.resolvedId,
targetType: identity.resolvedModelType,
targetLabel: getReferenceDisplayName(
qualified.qualified.hasMemberRef ? qualified.qualified.baseRefRaw : reference.raw,
identity.resolvedModel
),
relationKind: reference.relationKind,
section: reference.section,
field: reference.field,
sourceContext: reference.sourceContext,
notes: reference.notes
};
}
function groupOutboundRelationships(references, index) {
return groupRelationships(references, (reference) => {
const model = reference.targetPath ? index.modelsByFilePath[reference.targetPath] : null;
if (!model) {
return null;
}
return { model, relationKind: "outbound" };
});
}
function groupInboundRelationships(references, index) {
return groupRelationships(references, (reference) => {
const model = index.modelsByFilePath[reference.sourcePath];
if (!model) {
return null;
}
return { model, relationKind: "inbound" };
});
}
function groupRelationships(references, getGroupModel) {
const groups = /* @__PURE__ */ new Map();
for (const reference of references) {
const groupModel = getGroupModel(reference);
if (!groupModel) {
continue;
}
const { model, relationKind } = groupModel;
const key = model.path;
const existing = groups.get(key);
if (existing) {
existing.usages.push(reference);
existing.usageCount += 1;
continue;
}
groups.set(key, {
direction: reference.direction,
modelPath: model.path,
modelId: getModelId(model),
modelType: model.fileType,
modelLabel: getReferencedModelDisplayName(model),
usageCount: 1,
usages: [reference],
sourceLinks: groupImpactSourceLinks(
(model.sourceLinks ?? []).map(
(link) => createImpactSourceLink(model, link, relationKind)
)
)
});
}
return [...groups.values()].sort(
(left, right) => left.modelLabel.localeCompare(right.modelLabel)
);
}
function referenceTargetsModel(rawReference, model, index) {
if (referencesMatch(rawReference, model.path, index)) {
return true;
}
const modelId = getModelId(model);
if (modelId && referencesMatch(rawReference, modelId, index)) {
return true;
}
if (model.fileType !== "codeset") {
return false;
}
const qualified = resolveQualifiedMemberReference(rawReference, index);
if (!qualified.qualified.hasMemberRef) {
return false;
}
if (referencesMatch(qualified.qualified.baseRefRaw, model.path, index)) {
return true;
}
return Boolean(modelId && referencesMatch(qualified.qualified.baseRefRaw, modelId, index));
}
function groupValueUsages(model, inboundReferences, index) {
if (model.fileType !== "codeset") {
return [];
}
const byMember = /* @__PURE__ */ new Map();
for (const reference of inboundReferences) {
if (!isCodesetValueUsageSource(reference)) {
continue;
}
const structuredQualified = parseStructuredQualifiedReference(reference.targetRaw);
if (!structuredQualified) {
continue;
}
const qualified = resolveQualifiedMemberReference(reference.targetRaw, index);
if (!structuredQualified.memberRef || !referenceTargetsModel(qualified.qualified.baseRefRaw, model, index)) {
continue;
}
const entry = byMember.get(structuredQualified.memberRef) ?? {
memberLabel: qualified.member?.displayName,
references: []
};
if (!entry.memberLabel && qualified.member?.displayName) {
entry.memberLabel = qualified.member.displayName;
}
entry.references.push(reference);
byMember.set(structuredQualified.memberRef, entry);
}
return [...byMember.entries()].map(([member, entry]) => ({
member,
memberLabel: entry.memberLabel,
relationships: groupInboundRelationships(entry.references, index)
})).sort((left, right) => left.member.localeCompare(right.member));
}
function isCodesetValueUsageSource(reference) {
return reference.sourceType === "data-object" && reference.section === "Fields" && reference.field === "ref" || reference.sourceType === "screen" && reference.section === "Fields" && reference.field === "ref" || reference.sourceType === "screen" && (reference.section === "Fields" || reference.section === "Actions" || reference.section === "Messages") && reference.field === "condition" || reference.sourceType === "screen" && reference.section === "Local Processes" && (reference.field === "Steps.condition" || reference.field === "Errors.condition") || reference.sourceType === "app-process" && (reference.section === "Inputs" || reference.section === "Outputs") && reference.field === "data" || reference.sourceType === "app-process" && reference.section === "Flows" && reference.field === "condition" || reference.sourceType === "rule" && reference.section === "References" && reference.field === "ref" || reference.sourceType === "mapping" && (reference.section === "Scope" && reference.field === "ref" || reference.section === "Mappings" && reference.field === "rule");
}
function parseStructuredQualifiedReference(reference) {
const trimmed = reference?.trim();
if (!trimmed) {
return null;
}
const qualified = parseQualifiedRef(trimmed);
if (!qualified?.hasMemberRef || !qualified.memberRef) {
return null;
}
return isExternalModelReference(qualified.baseRefRaw) ? qualified : null;
}
function formatScreenFieldContext(field) {
return [field.id, field.label].filter(Boolean).join(" / ");
}
function formatScreenActionContext(action) {
const identity = [action.id, action.label].filter(Boolean).join(" / ");
const trigger = [action.target, action.event].filter(Boolean).join(" / ");
return [identity, trigger].filter(Boolean).join("; ");
}
function formatScreenMessageContext(message) {
return [message.id, message.timing].filter(Boolean).join(" / ");
}
function formatScreenLocalProcessRowContext(processId, row) {
return [processId, row.id].filter(Boolean).join(" / ");
}
function collectRelatedSourceLinks(model, outbound, inbound, index) {
const links = [];
const addLinks = (owner, relationKind) => {
if (!owner) {
return;
}
for (const link of owner.sourceLinks ?? []) {
links.push(createImpactSourceLink(owner, link, relationKind));
}
};
addLinks(model, "self");
for (const reference of outbound) {
addLinks(reference.targetPath ? index.modelsByFilePath[reference.targetPath] : null, "outbound");
}
for (const reference of inbound) {
addLinks(index.modelsByFilePath[reference.sourcePath], "inbound");
}
return groupImpactSourceLinks(links);
}
function createImpactSourceLink(owner, link, relationKind) {
return {
ownerPath: owner.path,
ownerId: getModelId(owner),
ownerType: owner.fileType,
ownerLabel: getReferencedModelDisplayName(owner),
path: link.path,
label: link.label,
notes: link.notes?.trim() ? [link.notes.trim()] : [],
relationKind
};
}
function groupImpactSourceLinks(sourceLinks) {
const groups = /* @__PURE__ */ new Map();
for (const link of sourceLinks) {
const key = [
link.relationKind,
link.ownerLabel,
link.path
].join("::");
const existing = groups.get(key);
if (existing) {
if (!existing.label && link.label) {
existing.label = link.label;
}
for (const note of link.notes) {
if (note && !existing.notes.includes(note)) {
existing.notes.push(note);
}
}
continue;
}
groups.set(key, {
...link,
notes: [...new Set(link.notes.filter(Boolean))]
});
}
return [...groups.values()].sort(
(left, right) => [left.relationKind, left.ownerLabel, left.path].join("|").localeCompare([right.relationKind, right.ownerLabel, right.path].join("|"))
);
}
function isExternalModelReference(reference) {
return extractModelReferenceCandidates(reference).length > 0;
}
function formatCategorizedRelationshipSection(title, relationships) {
if (relationships.length === 0) {
return `${title}
- none`;
}
const lines = [title];
const groups = groupImpactRelationshipsByCategory(relationships);
for (const key of IMPACT_RELATIONSHIP_CATEGORY_ORDER) {
const group = groups.get(key) ?? [];
if (group.length === 0) {
continue;
}
lines.push("", `### ${getImpactRelationshipCategoryMarkdownLabel(key)}`);
for (const relationship of dedupeImpactRelationships(group)) {
const usageText = relationship.usageCount === 1 ? "1 usage" : `${relationship.usageCount} usages`;
const idText = relationship.modelId && relationship.modelId !== relationship.modelLabel ? ` (${relationship.modelId})` : "";
lines.push(`- ${relationship.modelLabel}${idText} \u2014 ${usageText}`);
}
}
return lines.join("\n");
}
function groupImpactRelationshipsByCategory(relationships) {
const groups = /* @__PURE__ */ new Map();
for (const relationship of relationships) {
const key = getImpactRelationshipCategoryKey(relationship);
const group = groups.get(key) ?? [];
group.push(relationship);
groups.set(key, group);
}
return groups;
}
function dedupeImpactRelationships(relationships) {
const seen = /* @__PURE__ */ new Set();
const deduped = [];
for (const relationship of relationships) {
const key = relationship.modelPath || relationship.modelId || `${relationship.modelType}:${relationship.modelLabel}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
deduped.push(relationship);
}
return deduped;
}
function getImpactRelationshipCategoryMarkdownLabel(key) {
switch (key) {
case "screens":
return "Screens";
case "processes":
return "Processes";
case "rules":
return "Rules";
case "mappings":
return "Mappings";
case "diagrams":
return "Diagrams";
case "classes":
return "Classes";
case "dataEr":
return "Data / ER";
case "other":
default:
return "Other models";
}
}
function formatValueUsageSection(title, valueUsages) {
if (valueUsages.length === 0) {
return `${title}
- none`;
}
const lines = [title];
for (const valueUsage of valueUsages) {
lines.push(`- ${valueUsage.member}:`);
if (valueUsage.relationships.length === 0) {
lines.push(" - none");
continue;
}
for (const relationship of valueUsage.relationships) {
for (const usage of relationship.usages) {
const context = formatValueUsageContext(usage);
lines.push(
` - ${relationship.modelLabel} (${relationship.modelType}; 1 usage${context ? `; ${context}` : ""})`
);
}
}
}
return lines.join("\n");
}
function formatValueUsageContext(reference) {
return [formatReferenceLocation(reference), reference.sourceContext].filter(Boolean).join("; ");
}
function formatReferenceLocation(reference) {
return [reference.section, reference.field].filter(Boolean).join(".");
}
function formatUnresolvedSection(title, references) {
if (references.length === 0) {
return `${title}
- none`;
}
return [
title,
...references.map((reference) => {
const location = [reference.section, reference.field].filter(Boolean).join("/");
return `- ${reference.targetRaw} (${[reference.relationKind, location].filter(Boolean).join("; ")})`;
})
].join("\n");
}
function formatSourceLinkCountSection(title, sourceLinks) {
return [
title,
`- total: ${sourceLinks.length}`
].join("\n");
}
function getModelId(model) {
switch (model.fileType) {
case "object":
return typeof model.frontmatter.id === "string" && model.frontmatter.id.trim() ? model.frontmatter.id.trim() : model.name;
case "er-entity":
case "dfd-object":
case "dfd-diagram":
case "flow-diagram":
case "data-object":
case "app-process":
case "screen":
case "codeset":
case "message":
case "rule":
case "mapping":
case "domains":
return model.id;
case "diagram":
return model.name;
case "relations":
return typeof model.frontmatter.id === "string" && model.frontmatter.id.trim() ? model.frontmatter.id.trim() : void 0;
case "markdown":
default:
return void 0;
}
}
// src/core/weave-map.ts
function buildWeaveMapModel(summary, options = {}) {
const sourceLinkMode = options.sourceLinkMode ?? "compact";
const focusNodeId = createFocusNodeId(summary);
const nodes = /* @__PURE__ */ new Map();
const edges = /* @__PURE__ */ new Map();
const countedNodes = /* @__PURE__ */ new Map();
const addNode = (node) => {
const existing = nodes.get(node.id);
if (existing) {
return existing;
}
nodes.set(node.id, node);
return node;
};
const addCountedNode = (node, notes) => {
const existing = nodes.get(node.id);
const accumulator = countedNodes.get(node.id);
if (existing && accumulator) {
accumulator.count += 1;
if (notes) {
accumulator.notes.add(notes);
}
existing.label = appendCount(accumulator.baseLabel, accumulator.count);
existing.notes = mergeNotes(accumulator.notes);
return existing;
}
if (existing) {
return existing;
}
const noteSet = /* @__PURE__ */ new Set();
if (notes) {
noteSet.add(notes);
}
countedNodes.set(node.id, {
node,
baseLabel: node.label,
count: 1,
notes: noteSet
});
nodes.set(node.id, node);
return node;
};
const addEdge = (edge) => {
const key = createEdgeAggregationKey(edge);
const existing = edges.get(key);
if (existing) {
existing.count += 1;
if (edge.notes) {
existing.notes.add(edge.notes);
}
existing.edge.label = appendCount(existing.baseLabel, existing.count);
existing.edge.notes = mergeNotes(existing.notes);
return;
}
const notes = /* @__PURE__ */ new Set();
if (edge.notes) {
notes.add(edge.notes);
}
edges.set(key, {
edge,
baseLabel: edge.label || edge.relationType,
count: 1,
notes
});
};
addNode({
id: focusNodeId,
label: summary.modelLabel || summary.modelId || summary.modelPath,
modelType: summary.modelType,
layer: getWeaveMapLayerForModelType(summary.modelType),
path: summary.modelPath,
modelId: summary.modelId,
status: "focus"
});
summary.outboundRelationships.forEach((relationship, index) => {
const targetNode = addNode(createModelNode(relationship));
const relationType = getRelationshipRelationType(relationship, "outbound");
addEdge({
id: createEdgeId("outbound", focusNodeId, targetNode.id, index),
from: focusNodeId,
to: targetNode.id,
relationType,
label: relationType,
status: "ok",
notes: formatRelationshipNotes(relationship)
});
});
summary.inboundRelationships.forEach((relationship, index) => {
const sourceNode = addNode(createModelNode(relationship));
const relationType = getRelationshipRelationType(relationship, "inbound");
addEdge({
id: createEdgeId("inbound", sourceNode.id, focusNodeId, index),
from: sourceNode.id,
to: focusNodeId,
relationType,
label: relationType,
status: "ok",
notes: formatRelationshipNotes(relationship)
});
});
summary.unresolvedOutbound.forEach((reference, index) => {
const unresolvedNodeId = createUnresolvedNodeId(reference);
const notes = formatReferenceNotes(reference);
addCountedNode({
id: unresolvedNodeId,
label: reference.targetLabel || reference.targetRaw,
modelType: "unresolved",
layer: "Warning",
status: "unresolved",
notes
}, notes);
addEdge({
id: createEdgeId("unresolved", focusNodeId, unresolvedNodeId, index),
from: focusNodeId,
to: unresolvedNodeId,
relationType: "unresolved",
label: reference.relationKind || "unresolved",
status: "unresolved",
notes
});
});
if (sourceLinkMode === "compact") {
const sourceLinksByNode = /* @__PURE__ */ new Map();
summary.relatedSourceLinks.forEach((sourceLink) => {
const sourceNodeId = createSourceNodeId(sourceLink);
const notes = formatSourceLinkNotes(sourceLink);
addCountedNode({
id: sourceNodeId,
label: sourceLink.label || sourceLink.path,
modelType: "source-link",
layer: "Source",
path: sourceLink.path,
status: "source",
notes
}, notes);
const entry = sourceLinksByNode.get(sourceNodeId);
if (entry) {
entry.count += 1;
if (notes) {
entry.notes.push(notes);
}
return;
}
sourceLinksByNode.set(sourceNodeId, {
nodeId: sourceNodeId,
notes: notes ? [notes] : [],
count: 1
});
});
Array.from(sourceLinksByNode.values()).forEach((entry, index) => {
const edgeLabel = appendCount("source links", entry.count);
addEdge({
id: createEdgeId("source", focusNodeId, entry.nodeId, index),
from: focusNodeId,
to: entry.nodeId,
relationType: "source-link",
label: edgeLabel,
status: "source",
notes: mergeNotes(new Set(entry.notes))
});
});
} else {
summary.relatedSourceLinks.forEach((sourceLink, index) => {
const sourceNodeId = createSourceNodeId(sourceLink);
const notes = formatSourceLinkNotes(sourceLink);
addCountedNode({
id: sourceNodeId,
label: sourceLink.label || sourceLink.path,
modelType: "source-link",
layer: "Source",
path: sourceLink.path,
status: "source",
notes
}, notes);
const ownerNodeId = findSourceOwnerNodeId(sourceLink, nodes) ?? focusNodeId;
addEdge({
id: createEdgeId("source", ownerNodeId, sourceNodeId, index),
from: ownerNodeId,
to: sourceNodeId,
relationType: "source-link",
label: sourceLink.relationKind,
status: "source",
notes
});
});
}
return {
focusNodeId,
nodes: Array.from(nodes.values()),
edges: Array.from(edges.values()).map((entry) => entry.edge)
};
}
function getWeaveMapLayerForModelType(modelType) {
switch (modelType) {
case "screen":
return "UI";
case "app-process":
case "app_process":
return "Process";
case "rule":
return "Rule";
case "codeset":
return "Rule / State";
case "message":
return "UI / Message";
case "data-object":
case "data_object":
case "er-entity":
case "er_entity":
return "Data";
case "mapping":
return "Mapping";
case "object":
case "class":
case "class-diagram":
case "class_diagram":
case "diagram":
return "Implementation";
case "dfd-object":
case "dfd_object":
case "dfd-diagram":
case "dfd_diagram":
return "Data Flow";
case "flow-diagram":
case "flow_diagram":
return "Process";
case "relations":
return "Relationship";
case "source-link":
case "source_link":
return "Source";
case "unresolved":
return "Warning";
default:
return "Other";
}
}
function createFocusNodeId(summary) {
return `node:focus:${summary.modelPath || summary.modelId || summary.modelLabel}`;
}
function createModelNode(relationship) {
return {
id: createModelNodeId(relationship.modelPath, relationship.modelId),
label: relationship.modelLabel || relationship.modelId || relationship.modelPath,
modelType: relationship.modelType,
layer: getWeaveMapLayerForModelType(relationship.modelType),
path: relationship.modelPath,
modelId: relationship.modelId,
status: "ok",
notes: formatRelationshipNotes(relationship)
};
}
function createModelNodeId(modelPath, modelId) {
return `node:model:${modelId || modelPath}`;
}
function createSourceNodeId(sourceLink) {
return `node:source:${sourceLink.path.trim()}`;
}
function createUnresolvedNodeId(reference) {
return `node:unresolved:${getReferenceTargetIdentity(reference)}`;
}
function createEdgeId(relation, from, to, index) {
return `edge:${relation}:${from}:${to}:${index}`;
}
function getRelationshipRelationType(relationship, fallback) {
return relationship.usages.find((usage) => usage.relationKind)?.relationKind ?? fallback;
}
function getReferenceTargetIdentity(reference) {
return (reference.targetPath || reference.targetId || reference.targetRaw || reference.targetLabel).trim();
}
function createEdgeAggregationKey(edge) {
return [
edge.from,
edge.to,
edge.status,
edge.relationType,
edge.label ?? ""
].join("\0");
}
function appendCount(label, count) {
return count > 1 ? `${label} \xD7 ${count}` : label;
}
function mergeNotes(notes) {
const merged = Array.from(notes).filter((note) => note.trim());
return merged.length > 0 ? merged.join("; ") : void 0;
}
function formatRelationshipNotes(relationship) {
const parts = [`${relationship.usageCount} usage${relationship.usageCount === 1 ? "" : "s"}`];
const sections = uniqueDefined(relationship.usages.map((usage) => usage.section));
if (sections.length > 0) {
parts.push(`sections: ${sections.join(", ")}`);
}
const fields = uniqueDefined(relationship.usages.map((usage) => usage.field));
if (fields.length > 0) {
parts.push(`fields: ${fields.join(", ")}`);
}
return parts.join("; ");
}
function formatReferenceNotes(reference) {
const parts = [
reference.section ? `section: ${reference.section}` : void 0,
reference.field ? `field: ${reference.field}` : void 0,
reference.sourceContext ? `context: ${reference.sourceContext}` : void 0,
reference.notes
].filter((part) => Boolean(part));
return parts.length > 0 ? parts.join("; ") : void 0;
}
function formatSourceLinkNotes(sourceLink) {
const parts = [
`owner: ${sourceLink.ownerLabel}`,
`path: ${sourceLink.path}`,
...sourceLink.notes
].filter((part) => part.trim());
return parts.length > 0 ? parts.join("; ") : void 0;
}
function findSourceOwnerNodeId(sourceLink, nodes) {
for (const node of nodes.values()) {
if (sourceLink.ownerPath && node.path === sourceLink.ownerPath || sourceLink.ownerId && node.modelId === sourceLink.ownerId) {
return node.id;
}
}
return void 0;
}
function uniqueDefined(values) {
return Array.from(new Set(values.filter((value) => Boolean(value))));
}
// src/parsers/markdown-sections.ts
var SECTION_HEADINGS = {
"# Summary": "Summary",
"## Summary": "Summary",
"## Overview": "Overview",
"## Attributes": "Attributes",
"## Methods": "Methods",
"## Layout": "Layout",
"## Fields": "Fields",
"## Actions": "Actions",
"## Messages": "Messages",
"## Format": "Format",
"## Records": "Records",
"## References": "References",
"## Conditions": "Conditions",
"## Values": "Values",
"## Colors": "Colors",
"## Scope": "Scope",
"## Mappings": "Mappings",
"## Rules": "Rules",
"## Triggers": "Triggers",
"## Inputs": "Inputs",
"## Steps": "Steps",
"## Outputs": "Outputs",
"## Transitions": "Transitions",
"## Errors": "Errors",
"## Local Processes": "Local Processes",
"## Notes": "Notes",
"## Relations": "Relations",
"## Source Links": "Source Links",
"## Domain Sources": "Domain Sources",
"## Flows": "Flows",
"## Objects": "Objects",
"## Domains": "Domains",
"## Columns": "Columns",
"## Indexes": "Indexes"
};
function extractMarkdownSections(body) {
const normalized = body.replace(/\r\n/g, "\n");
const sections = {};
let currentSection = null;
for (const line of normalized.split("\n")) {
const trimmed = line.trim();
const nextSection = SECTION_HEADINGS[trimmed];
if (nextSection) {
currentSection = nextSection;
sections[currentSection] = [];
continue;
}
if (/^#{1,6}\s+/.test(trimmed)) {
currentSection = null;
continue;
}
if (currentSection) {
sections[currentSection].push(line);
}
}
return sections;
}
// src/parsers/frontmatter-parser.ts
function parseFrontmatter(markdown) {
const normalized = markdown.replace(/\r\n/g, "\n");
const warnings = [];
if (!normalized.startsWith("---\n")) {
return {
file: {
body: normalized
},
warnings
};
}
const lines = normalized.split("\n");
let closingIndex = -1;
for (let index = 1; index < lines.length; index += 1) {
if (lines[index].trim() === "---") {
closingIndex = index;
break;
}
}
if (closingIndex === -1) {
warnings.push(createWarning2("frontmatter parse error: missing closing delimiter"));
return {
file: {
body: normalized
},
warnings
};
}
const frontmatterLines = lines.slice(1, closingIndex);
const body = lines.slice(closingIndex + 1).join("\n");
const parsed = parseYamlLikeFrontmatter(frontmatterLines);
warnings.push(...parsed.warnings);
return {
file: {
frontmatter: parsed.frontmatter,
body
},
warnings
};
}
function parseYamlLikeFrontmatter(lines) {
const warnings = [];
const frontmatter = {};
let activeListKey = null;
for (const rawLine of lines) {
const trimmed = rawLine.trim();
if (!trimmed || trimmed.startsWith("#")) {
continue;
}
const listItemMatch = rawLine.match(/^\s*-\s+(.+)$/);
if (listItemMatch) {
if (!activeListKey) {
warnings.push(
createWarning2(
`frontmatter parse error: unexpected list item "${trimmed}"`
)
);
continue;
}
const currentValue = frontmatter[activeListKey];
if (!Array.isArray(currentValue)) {
frontmatter[activeListKey] = [];
}
frontmatter[activeListKey].push(
parseScalarValue(listItemMatch[1].trim())
);
continue;
}
activeListKey = null;
const keyValueMatch = rawLine.match(/^\s*([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
if (!keyValueMatch) {
warnings.push(
createWarning2(`frontmatter parse error: malformed line "${trimmed}"`)
);
continue;
}
const [, key, rawValue] = keyValueMatch;
const value = rawValue.trim();
if (!value) {
frontmatter[key] = [];
activeListKey = key;
continue;
}
frontmatter[key] = parseScalarValue(value);
}
return {
frontmatter,
warnings
};
}
function parseScalarValue(value) {
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
if (/^\[(.*)\]$/.test(value)) {
const inner = value.slice(1, -1).trim();
if (!inner) {
return [];
}
return inner.split(",").map((entry) => stripQuotes(entry.trim()));
}
if (/^-?\d+(\.\d+)?$/.test(value)) {
return Number(value);
}
return stripQuotes(value);
}
function stripQuotes(value) {
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
return value.slice(1, -1);
}
return value;
}
function createWarning2(message) {
return {
code: "frontmatter-parse-error",
message,
severity: "warning"
};
}
// src/parsers/source-links-parser.ts
function parseSourceLinks(lines) {
if (!lines) {
return [];
}
const tableLinks = parseSourceLinksTable(lines);
if (tableLinks) {
return tableLinks;
}
return lines.map((line) => parseSourceLinkLine(line)).filter((link) => Boolean(link));
}
function parseSourceLinksTable(lines) {
const tableLines = lines.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (tableLines.length < 2) {
return null;
}
const headers = splitMarkdownTableRow(tableLines[0])?.map(
(header) => normalizeHeader(header)
);
if (!headers || headers.length === 0) {
return [];
}
const pathIndex = findHeaderIndex(headers, ["path", "source", "source_path", "file"]);
if (pathIndex < 0) {
return [];
}
const labelIndex = findHeaderIndex(headers, ["label", "name", "title"]);
const notesIndex = findHeaderIndex(headers, ["notes", "note", "description"]);
return tableLines.slice(2).map((line) => splitMarkdownTableRow(line) ?? []).map((cells) => ({
path: cleanSourcePath(cells[pathIndex]),
label: labelIndex >= 0 ? cleanOptionalValue(cells[labelIndex]) : void 0,
notes: notesIndex >= 0 ? cleanOptionalValue(cells[notesIndex]) : void 0
})).filter((link) => Boolean(link.path));
}
function parseSourceLinkLine(line) {
const trimmed = line.trim();
if (!trimmed) {
return null;
}
const withoutBullet = trimmed.replace(/^[-*]\s+/, "").trim();
if (!withoutBullet) {
return null;
}
const markdownLink = withoutBullet.match(/^\[([^\]]+)\]\(([^)]+)\)(?:\s*[-:]\s*(.+))?$/);
if (markdownLink) {
return {
path: cleanSourcePath(markdownLink[2]),
label: cleanOptionalValue(markdownLink[1]),
notes: cleanOptionalValue(markdownLink[3])
};
}
const [pathValue, notes] = splitPathAndNotes(withoutBullet);
const path2 = cleanSourcePath(pathValue);
return path2 ? {
path: path2,
notes: cleanOptionalValue(notes)
} : null;
}
function splitPathAndNotes(value) {
const separator = value.match(/\s+-\s+|\s+:\s+/);
if (!separator || separator.index === void 0) {
return [value, void 0];
}
return [
value.slice(0, separator.index),
value.slice(separator.index + separator[0].length)
];
}
function cleanSourcePath(value) {
return cleanOptionalValue(value)?.replace(/^`|`$/g, "").trim() ?? "";
}
function cleanOptionalValue(value) {
const cleaned = value?.trim();
return cleaned ? cleaned : void 0;
}
function normalizeHeader(value) {
return value.trim().toLowerCase().replace(/[\s-]+/g, "_");
}
function findHeaderIndex(headers, candidates) {
return headers.findIndex((header) => candidates.includes(header));
}
// src/parsers/domains-parser.ts
var DOMAIN_HEADERS = ["id", "name", "kind", "parent", "description"];
function parseDomainsFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const description = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
if (frontmatter.type !== "domains") {
warnings.push(createWarning3(path2, "type", 'expected type "domains"'));
}
if (!id) {
warnings.push(createWarning3(path2, "id", 'required frontmatter "id" is missing'));
}
const domainsTable = parseDomainEntries(sections.Domains, path2);
warnings.push(...domainsTable.warnings);
warnings.push(...validateDomainEntries(path2, domainsTable.rows));
const fallbackTitle = name || id || getFileStem(path2) || "Untitled Domains";
return {
file: {
fileType: "domains",
schema: "domains",
path: path2,
title: fallbackTitle,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackTitle,
description: description || void 0,
domains: domainsTable.rows
},
warnings
};
}
function parseDomainEntries(lines, path2) {
const table = parseMarkdownTable(lines, DOMAIN_HEADERS, path2, "Domains");
const warnings = [...table.warnings];
const rows = [];
const seenIds = /* @__PURE__ */ new Set();
table.rows.forEach((row, rowIndex) => {
const id = row.id?.trim() ?? "";
const name = row.name?.trim() ?? "";
const kind = row.kind?.trim() ?? "";
const parent = row.parent?.trim() ?? "";
const description = row.description?.trim() ?? "";
if (!id) {
warnings.push({
code: "invalid-structure",
message: formatDomainIdRequiredMessage(),
severity: "error",
path: path2,
field: "Domains.id",
context: { rowIndex: rowIndex + 1 }
});
return;
}
if (seenIds.has(id)) {
warnings.push({
code: "invalid-structure",
message: formatDuplicateDomainIdMessage(id),
severity: "error",
path: path2,
field: "Domains.id",
context: { rowIndex: rowIndex + 1 }
});
return;
}
seenIds.add(id);
rows.push({
id,
name: name || void 0,
kind: kind || void 0,
parent: parent || void 0,
description: description || void 0,
rowIndex
});
});
return { rows, warnings };
}
function validateDomainEntries(path2, domains, options = {}) {
const warnings = [];
const domainIds = new Set(domains.map((domain) => domain.id));
for (const domain of domains) {
if (!domain.parent) {
continue;
}
if (domain.parent === domain.id) {
warnings.push({
code: "invalid-structure",
message: formatDomainSelfParentMessage(domain.id),
severity: "error",
path: path2,
field: "Domains.parent",
context: { rowIndex: domain.rowIndex + 1 }
});
continue;
}
if (!domainIds.has(domain.parent) && !options.skipUnknownParents) {
warnings.push({
code: "unresolved-reference",
message: formatDomainParentUnknownMessage(domain.parent),
severity: "warning",
path: path2,
field: "Domains.parent",
context: { rowIndex: domain.rowIndex + 1 }
});
}
}
warnings.push(...validateDomainCycles(path2, domains));
return warnings;
}
function validateDomainCycles(path2, domains) {
const warnings = [];
const byId = new Map(domains.map((domain) => [domain.id, domain]));
const reported = /* @__PURE__ */ new Set();
for (const domain of domains) {
if (domain.parent === domain.id) {
continue;
}
const chain = [];
const seen = /* @__PURE__ */ new Set();
let current = domain;
while (current?.parent) {
chain.push(current.id);
if (seen.has(current.parent)) {
const cycleStart = chain.indexOf(current.parent);
const cycleIds = cycleStart >= 0 ? chain.slice(cycleStart) : [current.parent, current.id];
const cycleKey = [...new Set(cycleIds)].sort().join(">");
if (!reported.has(cycleKey)) {
reported.add(cycleKey);
warnings.push({
code: "invalid-structure",
message: formatDomainParentCycleMessage([...cycleIds, current.parent]),
severity: "error",
path: path2,
field: "Domains.parent",
context: { rowIndex: domain.rowIndex + 1 }
});
}
break;
}
seen.add(current.id);
current = byId.get(current.parent);
}
}
return warnings;
}
function createWarning3(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
function getFileStem(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
// src/core/relation-resolver.ts
function resolveDiagramRelations(diagram, index) {
if (diagram.kind === "er") {
return resolveErDiagramRelations(diagram, index);
}
if (diagram.kind === "dfd" || diagram.schema === "flow_diagram") {
return resolveDfdDiagramRelations(diagram, index);
}
const warnings = [];
const presentObjectIds = /* @__PURE__ */ new Set();
const deduped = dedupeDiagramNodes(
diagram,
(objectRef) => resolveObjectModelReference(objectRef, index) ?? void 0,
(object, objectRef) => object ? getObjectId2(object) : objectRef,
(object, objectRef) => object ? getObjectId2(object) : `ref:${objectRef}`,
(object, objectRef) => getClassDiagramNodeDisplayName(objectRef, object),
(objectRef) => `unresolved object ref "${objectRef}"`,
"Objects"
);
for (const node of deduped.nodes) {
if (node.object) {
presentObjectIds.add(getObjectId2(node.object));
}
}
const edges = resolveEdges(diagram, index, presentObjectIds, warnings);
return {
diagram,
nodes: deduped.nodes,
edges,
missingObjects: deduped.missingObjects,
warnings: [...warnings, ...deduped.warnings]
};
}
function resolveErDiagramRelations(diagram, index) {
const warnings = [];
const presentEntities = [];
const deduped = dedupeDiagramNodes(
diagram,
(objectRef) => resolveErEntityReference(objectRef, index) ?? void 0,
(entity, objectRef) => entity?.id ?? objectRef,
(entity, objectRef) => entity?.id ?? `ref:${objectRef}`,
(entity, objectRef) => getErDiagramNodeDisplayName(objectRef, entity),
(objectRef) => `unresolved ER entity ref "${objectRef}"`,
"Objects"
);
for (const node of deduped.nodes) {
if (node.object) {
presentEntities.push(node.object);
}
}
return {
diagram,
nodes: deduped.nodes,
edges: resolveErEdges(diagram, index, presentEntities, warnings),
missingObjects: deduped.missingObjects,
warnings: [...warnings, ...deduped.warnings]
};
}
function resolveDfdDiagramRelations(diagram, index) {
const warnings = [];
const isFlowDiagram = diagram.schema === "flow_diagram";
const domainResolution = resolveDfdDiagramDomains(diagram, index);
warnings.push(...domainResolution.warnings);
const resolvedDiagram = {
...diagram,
domainSourceSummaries: domainResolution.sourceSummaries,
domains: domainResolution.domains
};
const objectResolution = resolveDfdDiagramObjects(resolvedDiagram, index, {
hasDomainSources: diagram.domainSources.length > 0
});
const hasUnreadableFlowObjects = isFlowDiagram && hasInvalidDfdLikeSectionHeader(diagram.path, index, "Objects");
const edges = [];
diagram.flows.forEach((flow, rowIndex) => {
const context = {
section: "Flows",
rowIndex: rowIndex + 1,
relatedId: flow.id
};
const sourceEntry = isFlowDiagram ? objectResolution.byId.get(flow.from.trim()) ?? null : resolveDfdFlowEndpoint(flow.from, objectResolution, index);
const targetEntry = isFlowDiagram ? objectResolution.byId.get(flow.to.trim()) ?? null : resolveDfdFlowEndpoint(flow.to, objectResolution, index);
if (!sourceEntry) {
if (hasUnreadableFlowObjects) {
return;
}
const listedObject = isFlowDiagram ? null : resolveDfdObjectReference(flow.from, index);
if (listedObject) {
warnings.push({
code: "unresolved-reference",
message: `flow source "${listedObject.id}" resolves to a dfd_object but is not listed in "Objects"`,
severity: "warning",
path: diagram.path,
field: "Flows",
context
});
}
warnings.push({
code: "unresolved-reference",
message: isFlowDiagram ? 'Flow Diagram flow source "' + flow.from + '" is not defined in the local ## Objects table.' : 'unresolved DFD flow source "' + flow.from + '"',
severity: "error",
path: diagram.path,
field: isFlowDiagram ? "Flows.from" : "Flows",
context: isFlowDiagram ? { ...context, referenceKind: "local-object-id" } : context
});
return;
}
if (!targetEntry) {
if (hasUnreadableFlowObjects) {
return;
}
const listedObject = isFlowDiagram ? null : resolveDfdObjectReference(flow.to, index);
if (listedObject) {
warnings.push({
code: "unresolved-reference",
message: `flow target "${listedObject.id}" resolves to a dfd_object but is not listed in "Objects"`,
severity: "warning",
path: diagram.path,
field: "Flows",
context
});
}
warnings.push({
code: "unresolved-reference",
message: isFlowDiagram ? 'Flow Diagram flow target "' + flow.to + '" is not defined in the local ## Objects table.' : 'unresolved DFD flow target "' + flow.to + '"',
severity: "error",
path: diagram.path,
field: isFlowDiagram ? "Flows.to" : "Flows",
context: isFlowDiagram ? { ...context, referenceKind: "local-object-id" } : context
});
return;
}
if (sourceEntry.node.id === targetEntry.node.id) {
warnings.push({
code: "invalid-structure",
message: `${isFlowDiagram ? "Flow Diagram" : "DFD"} flow "${flow.id ?? rowIndex + 1}" is a self-loop`,
severity: "warning",
path: diagram.path,
field: "Flows",
context
});
}
if (!isFlowDiagram) {
if (sourceEntry.kind === "external" && targetEntry.kind === "external") {
warnings.push(createDfdFlowShapeWarning(diagram.path, context, "external -> external"));
} else if (sourceEntry.kind === "external" && targetEntry.kind === "datastore") {
warnings.push(createDfdFlowShapeWarning(diagram.path, context, "external -> datastore"));
} else if (sourceEntry.kind === "datastore" && targetEntry.kind === "datastore") {
warnings.push(createDfdFlowShapeWarning(diagram.path, context, "datastore -> datastore"));
}
}
const flowData = resolveDfdFlowDataDisplay(flow.data, index);
warnings.push(...resolveDfdFlowDataReferenceWarnings(
diagram,
flow.data,
index,
context
));
edges.push({
id: flow.id,
source: sourceEntry.node.id,
target: targetEntry.node.id,
kind: "flow",
label: isFlowDiagram ? buildFlowDiagramEdgeLabel(flow, flowData.label) : flowData.label,
metadata: {
notes: flow.notes,
flowKind: flow.kind,
trigger: flow.trigger,
condition: flow.condition,
rowIndex,
sourceKind: sourceEntry.kind,
targetKind: targetEntry.kind,
dataRaw: flow.data,
dataReference: flowData.reference,
dataModelPath: flowData.model?.path
}
});
});
return {
diagram: resolvedDiagram,
nodes: objectResolution.nodes,
edges,
missingObjects: objectResolution.missingObjects,
warnings: [...warnings, ...objectResolution.warnings]
};
}
function hasInvalidDfdLikeSectionHeader(path2, index, section) {
return (index.warningsByFilePath[path2] ?? []).some(
(warning) => warning.code === "invalid-table-column" && getDiagnosticSectionName2(warning) === section.toLowerCase()
);
}
function getDiagnosticSectionName2(warning) {
const contextSection = typeof warning.context?.section === "string" ? warning.context.section : "";
const section = contextSection || warning.message.match(/section "([^"]+)"/i)?.[1] || warning.section || warning.field || "";
const normalized = section.split(".")[0]?.split(":")[0]?.trim().toLowerCase();
return normalized || null;
}
function resolveDfdDiagramDomains(diagram, index) {
const warnings = [];
const sourceSummaries = [];
const effectiveById = /* @__PURE__ */ new Map();
const order = [];
const validSources = [];
for (const sourceRef of diagram.domainSources) {
const resolved = findModelByReference(sourceRef.ref, index);
if (!resolved) {
sourceSummaries.push({
ref: sourceRef,
status: "unresolved",
domainCount: 0
});
warnings.push(createDfdDomainSourceWarning(
diagram.path,
sourceRef.rowIndex,
formatDomainDiagramUnresolvedSourceMessage(sourceRef.ref),
"unresolved-reference"
));
continue;
}
if (resolved.fileType !== "domains") {
sourceSummaries.push({
ref: sourceRef,
resolvedPath: resolved.path,
status: "invalid-type",
domainCount: 0
});
warnings.push(createDfdDomainSourceWarning(
diagram.path,
sourceRef.rowIndex,
formatDomainDiagramInvalidSourceTypeMessage(sourceRef.ref, resolved.fileType),
"invalid-structure"
));
continue;
}
sourceSummaries.push({
ref: sourceRef,
resolvedPath: resolved.path,
resolvedId: resolved.id,
status: resolved.domains.length > 0 ? "ok" : "empty",
domainCount: resolved.domains.length
});
if (resolved.domains.length === 0) {
warnings.push(createDfdDomainSourceWarning(
diagram.path,
sourceRef.rowIndex,
formatDomainDiagramEmptySourceMessage(sourceRef.ref),
"invalid-structure"
));
}
validSources.push({ source: resolved, ref: sourceRef.ref });
}
const sourceMerge = mergeDomainDiagramSources(validSources, diagram.path);
warnings.push(...sourceMerge.warnings);
for (const domain of sourceMerge.domains) {
order.push(domain.id);
effectiveById.set(domain.id, {
domain: { ...domain },
sourcePath: diagram.path
});
}
for (const domain of diagram.domains ?? []) {
const previous = effectiveById.get(domain.id);
if (!previous) {
order.push(domain.id);
effectiveById.set(domain.id, { domain: { ...domain }, sourcePath: diagram.path });
continue;
}
for (const field of ["name", "kind", "parent"]) {
const localValue = domain[field]?.trim() ?? "";
const sourceValue = previous.domain[field]?.trim() ?? "";
if (localValue && sourceValue && localValue !== sourceValue) {
warnings.push({
code: "invalid-structure",
message: diagram.schema === "flow_diagram" ? formatFlowDiagramLocalDomainOverridesSourceMessage(
domain.id,
field,
localValue,
sourceValue
) : formatDfdLocalDomainOverridesSourceMessage(
domain.id,
field,
localValue,
sourceValue
),
severity: "warning",
path: diagram.path,
field: `Domains.${field}`,
context: { rowIndex: domain.rowIndex + 1 }
});
}
}
effectiveById.set(domain.id, { domain: { ...domain }, sourcePath: diagram.path });
}
const domains = order.map((id) => effectiveById.get(id)?.domain).filter((domain) => Boolean(domain));
warnings.push(...validateDomainEntries(diagram.path, domains));
return { domains, sourceSummaries, warnings };
}
function createDfdDomainSourceWarning(path2, rowIndex, message, code) {
return {
code,
message,
severity: "warning",
path: path2,
field: "Domain Sources.ref",
context: { rowIndex: rowIndex + 1 }
};
}
function resolveDfdDiagramObjects(diagram, index, domainContext) {
const warnings = [];
const nodes = [];
const missingObjects = [];
const byId = /* @__PURE__ */ new Map();
const byReferenceKey = /* @__PURE__ */ new Map();
const entries = diagram.objectEntries.length > 0 ? diagram.objectEntries : diagram.objectRefs.map((ref, rowIndex) => ({
ref,
rowIndex,
compatibilityMode: "legacy_ref_only"
}));
const localDomainIds = new Set((diagram.domains ?? []).map((domain) => domain.id));
for (const entry of entries) {
const ref = entry.ref?.trim();
const resolvedObject = ref ? resolveDfdObjectReference(ref, index) ?? void 0 : void 0;
const resolvedIdentity = ref ? resolveReferenceIdentity(ref, index) : void 0;
if (!ref) {
if (diagram.schema === "dfd_diagram") {
warnings.push({
code: "invalid-structure",
message: `DFD local object "${entry.id ?? entry.label ?? entry.rowIndex + 1}" is treated as an inline object without ref.`,
severity: "info",
path: diagram.path,
field: "Objects",
context: { rowIndex: entry.rowIndex + 1 }
});
}
} else if (!resolvedObject && !resolvedIdentity?.resolvedModel) {
missingObjects.push(ref);
warnings.push({
code: "unresolved-reference",
message: diagram.schema === "flow_diagram" ? `unresolved Flow Diagram object ref "${ref}"` : `unresolved DFD object ref "${ref}"`,
severity: "warning",
path: diagram.path,
field: "Objects",
context: { rowIndex: entry.rowIndex + 1 }
});
}
const effectiveKind = entry.kind ?? resolvedObject?.kind ?? (diagram.schema === "flow_diagram" ? "unknown" : "other");
if (diagram.schema === "dfd_diagram" && !entry.kind && !resolvedObject?.kind) {
warnings.push({
code: "invalid-structure",
message: `DFD object "${entry.id ?? ref ?? entry.rowIndex + 1}" has no kind, and it could not be inferred from ref.`,
severity: "warning",
path: diagram.path,
field: "Objects",
context: { rowIndex: entry.rowIndex + 1 }
});
}
const resolvedLabel = getDfdDiagramNodeDisplayName(entry, resolvedObject);
const nodeId = entry.id?.trim() || resolvedObject?.id || ref || `dfd-object-${entry.rowIndex + 1}`;
const domain = entry.domain?.trim();
if (diagram.schema === "dfd_diagram" && domain && localDomainIds.size === 0 && !domainContext.hasDomainSources) {
warnings.push({
code: "unresolved-reference",
message: formatDfdObjectDomainWithoutLocalDomainsMessage(
entry.id ?? ref ?? String(entry.rowIndex + 1),
domain
),
severity: "warning",
path: diagram.path,
field: "Objects.domain",
context: { rowIndex: entry.rowIndex + 1 }
});
} else if (domain && !localDomainIds.has(domain) && (diagram.schema === "dfd_diagram" || localDomainIds.size > 0 || domainContext.hasDomainSources)) {
warnings.push({
code: "unresolved-reference",
message: diagram.schema === "flow_diagram" ? domainContext.hasDomainSources ? formatFlowDiagramObjectUnknownDomainMessage(
entry.id ?? ref ?? String(entry.rowIndex + 1),
domain
) : formatFlowDiagramObjectUnknownLocalDomainMessage(
entry.id ?? ref ?? String(entry.rowIndex + 1),
domain
) : domainContext.hasDomainSources ? formatDfdObjectUnknownDomainMessage(
entry.id ?? ref ?? String(entry.rowIndex + 1),
domain
) : formatDfdObjectUnknownLocalDomainMessage(
entry.id ?? ref ?? String(entry.rowIndex + 1),
domain
),
severity: "warning",
path: diagram.path,
field: "Objects.domain",
context: { rowIndex: entry.rowIndex + 1 }
});
}
const node = {
id: nodeId,
ref,
label: resolvedLabel,
kind: effectiveKind,
metadata: {
notes: entry.notes,
domain,
rowIndex: entry.rowIndex,
local: !ref,
refReference: resolvedIdentity?.parsed,
refModelPath: resolvedIdentity?.resolvedModel?.path,
refModelType: resolvedIdentity?.resolvedModel?.fileType,
compatibilityMode: entry.compatibilityMode
},
object: resolvedObject
};
const registryEntry = {
entry,
node,
object: resolvedObject,
kind: effectiveKind
};
nodes.push(node);
if (entry.id?.trim()) {
byId.set(entry.id.trim(), registryEntry);
}
const keySourceRefs = [
ref,
resolvedObject?.id,
resolvedObject?.path
].filter((value) => Boolean(value && value.trim()));
for (const sourceRef of keySourceRefs) {
const keys = buildReferenceIdentityKeys(resolveReferenceIdentity(sourceRef, index));
for (const key of keys) {
if (!byReferenceKey.has(key)) {
byReferenceKey.set(key, registryEntry);
}
}
}
}
return { nodes, missingObjects, warnings, byId, byReferenceKey };
}
function resolveDfdFlowEndpoint(value, registry, index) {
const trimmed = value.trim();
if (!trimmed) {
return null;
}
const byId = registry.byId.get(trimmed);
if (byId) {
return byId;
}
for (const key of buildReferenceIdentityKeys(resolveReferenceIdentity(trimmed, index))) {
const matched = registry.byReferenceKey.get(key);
if (matched) {
return matched;
}
}
return null;
}
function buildFlowDiagramEdgeLabel(flow, dataLabel) {
const kind = flow.kind?.trim();
const trigger = flow.trigger?.trim();
const data = dataLabel?.trim();
if (trigger && data) {
return `${trigger} / ${data}`;
}
if (kind && data) {
return `${kind} / ${data}`;
}
return data || trigger || kind || void 0;
}
function resolveDfdFlowDataReferenceWarnings(diagram, rawValue, index, context) {
const wikilinks = rawValue ? extractWikilinkReferences(rawValue) : [];
if (wikilinks.length === 0) {
return [];
}
const diagramLabel = diagram.schema === "flow_diagram" ? "Flow Diagram" : "DFD";
return wikilinks.filter((reference) => !resolveReferenceIdentity(reference, index).resolvedModel).map((reference) => ({
code: "unresolved-reference",
message: `${diagramLabel} flow data reference "${reference}" could not be resolved. Check the data/model id or file name.`,
severity: "warning",
path: diagram.path,
field: "data",
context: {
...context,
referenceValue: reference
}
}));
}
function resolveDfdFlowDataDisplay(rawValue, index) {
const trimmed = rawValue?.trim();
if (!trimmed) {
return {};
}
const reference = parseReferenceValue(trimmed);
if (!reference) {
return { label: trimmed };
}
if (reference.kind === "raw") {
return { label: trimmed, reference };
}
if (reference.isExternal) {
return {
label: reference.display || trimmed,
reference
};
}
const model = reference.target ? resolveDataObjectReference(reference.target, index) ?? findModelByReference(reference.target, index) : null;
if (reference.display) {
return {
label: reference.display,
reference,
model
};
}
if (model) {
return {
label: getReferenceDisplayName(trimmed, model),
reference,
model
};
}
if (reference.target) {
return {
label: getReferenceDisplayName(trimmed),
reference
};
}
return { label: getReferenceDisplayName(trimmed), reference };
}
function dedupeDiagramNodes(diagram, resolveObject, buildResolvedId, buildCanonicalKey, buildDisplayName, buildUnresolvedMessage, field = "objectRefs") {
const nodes = [];
const missingObjects = [];
const warnings = [];
const seenKeys = /* @__PURE__ */ new Set();
const seenMissingRefs = /* @__PURE__ */ new Set();
const duplicateCounts = /* @__PURE__ */ new Map();
for (const objectRef of diagram.objectRefs) {
const object = resolveObject(objectRef);
const canonicalKey = buildCanonicalKey(object, objectRef);
if (seenKeys.has(canonicalKey)) {
const existing = duplicateCounts.get(canonicalKey);
if (existing) {
existing.count += 1;
} else {
duplicateCounts.set(canonicalKey, {
displayRef: objectRef,
count: 2
});
}
continue;
}
seenKeys.add(canonicalKey);
if (!object && !seenMissingRefs.has(objectRef)) {
seenMissingRefs.add(objectRef);
missingObjects.push(objectRef);
warnings.push({
code: "unresolved-reference",
message: buildUnresolvedMessage(objectRef),
severity: "warning",
path: diagram.path,
field
});
}
nodes.push({
id: buildResolvedId(object, objectRef),
ref: objectRef,
label: buildDisplayName(object, objectRef),
object
});
}
if (duplicateCounts.size > 0) {
const summary = Array.from(duplicateCounts.values()).map((entry) => `${entry.displayRef} x${entry.count}`).join(", ");
warnings.push({
code: "invalid-structure",
message: `Duplicate object refs were merged: ${summary}`,
severity: "info",
path: diagram.path,
field
});
}
return {
nodes,
missingObjects,
warnings
};
}
function resolveEdges(diagram, index, presentObjectIds, warnings) {
const explicitEdges = diagram.edges.filter((edge) => {
const sourceObject = resolveObjectModelReference(edge.source, index);
const targetObject = resolveObjectModelReference(edge.target, index);
if (!sourceObject || !targetObject) {
const sourceIdentity = sourceObject ? void 0 : resolveReferenceIdentity(edge.source, index);
const targetIdentity = targetObject ? void 0 : resolveReferenceIdentity(edge.target, index);
const sourceEndpointExists = Boolean(sourceObject || sourceIdentity?.resolvedModel);
const targetEndpointExists = Boolean(targetObject || targetIdentity?.resolvedModel);
if (sourceEndpointExists && targetEndpointExists) {
pushClassRelationTargetNotDiagramCompatibleWarnings(
warnings,
diagram.path,
[
{ reference: edge.source, object: sourceObject, identity: sourceIdentity },
{ reference: edge.target, object: targetObject, identity: targetIdentity }
]
);
return false;
}
warnings.push({
code: "unresolved-reference",
message: `unresolved relation endpoint in relation "${edge.id ?? `${edge.source}:${edge.target}`}"`,
severity: "warning",
path: diagram.path,
field: "relations"
});
return false;
}
const sourceId = getObjectId2(sourceObject);
const targetId = getObjectId2(targetObject);
if (!presentObjectIds.has(sourceId) || !presentObjectIds.has(targetId)) {
warnings.push({
code: "unresolved-reference",
message: `relation "${edge.id ?? `${edge.source}:${edge.target}`}" is outside diagram scope`,
severity: "info",
path: diagram.path,
field: "relations"
});
return false;
}
edge.source = sourceId;
edge.target = targetId;
return true;
});
if (explicitEdges.length > 0) {
return explicitEdges;
}
const autoAggregatedEdges = resolveClassDiagramEdgesFromObjects(
diagram,
index,
presentObjectIds,
warnings
);
if (autoAggregatedEdges.length > 0) {
warnings.push({
code: "section-missing",
message: 'diagram relations are empty; using auto-collected class relations from "Objects"',
severity: "info",
path: diagram.path,
field: "relations"
});
return autoAggregatedEdges;
}
const edges = [];
const seenRelationIds = /* @__PURE__ */ new Set();
for (const objectId of presentObjectIds) {
const relations = index.relationsByObjectId[objectId] ?? [];
for (const relation of relations) {
const relationKey = relation.id ?? buildRelationKey2(relation);
if (seenRelationIds.has(relationKey)) {
continue;
}
seenRelationIds.add(relationKey);
const sourceObject = resolveObjectModelReference(relation.source, index);
const targetObject = resolveObjectModelReference(relation.target, index);
if (!sourceObject || !targetObject) {
const sourceIdentity = sourceObject ? void 0 : resolveReferenceIdentity(relation.source, index);
const targetIdentity = targetObject ? void 0 : resolveReferenceIdentity(relation.target, index);
const sourceEndpointExists = Boolean(sourceObject || sourceIdentity?.resolvedModel);
const targetEndpointExists = Boolean(targetObject || targetIdentity?.resolvedModel);
if (sourceEndpointExists && targetEndpointExists) {
pushClassRelationTargetNotDiagramCompatibleWarnings(
warnings,
diagram.path,
[
{ reference: relation.source, object: sourceObject, identity: sourceIdentity },
{ reference: relation.target, object: targetObject, identity: targetIdentity }
]
);
seenRelationIds.add(relationKey);
continue;
}
warnings.push({
code: "unresolved-reference",
message: `unresolved relation endpoint in relation "${relation.id ?? relationKey}"`,
severity: "warning",
path: diagram.path,
field: "relations"
});
continue;
}
if (presentObjectIds.has(getObjectId2(sourceObject)) && presentObjectIds.has(getObjectId2(targetObject))) {
edges.push(toDiagramEdge(relation, sourceObject, targetObject));
}
}
}
return edges;
}
function resolveClassDiagramEdgesFromObjects(diagram, index, presentObjectIds, warnings) {
const edges = [];
const seenRelationIds = /* @__PURE__ */ new Set();
for (const objectId of presentObjectIds) {
const object = index.objectsById[objectId];
if (!object) {
continue;
}
for (const relation of object.relations) {
const sourceObject = resolveObjectModelReference(relation.sourceClass, index);
const targetObject = resolveObjectModelReference(relation.targetClass, index);
const relationKey = buildClassRelationKey2(relation);
if (seenRelationIds.has(relationKey)) {
continue;
}
const sourceIdentity = sourceObject ? void 0 : resolveReferenceIdentity(relation.sourceClass, index);
const targetIdentity = targetObject ? void 0 : resolveReferenceIdentity(relation.targetClass, index);
const sourceEndpointExists = Boolean(sourceObject || sourceIdentity?.resolvedModel);
const targetEndpointExists = Boolean(targetObject || targetIdentity?.resolvedModel);
if (!sourceObject || !targetObject) {
if (sourceEndpointExists && targetEndpointExists) {
pushClassRelationTargetNotDiagramCompatibleWarnings(
warnings,
diagram.path,
[
{ reference: relation.sourceClass, object: sourceObject, identity: sourceIdentity },
{ reference: relation.targetClass, object: targetObject, identity: targetIdentity }
]
);
seenRelationIds.add(relationKey);
continue;
}
warnings.push({
code: "unresolved-reference",
message: `unresolved class relation endpoint in relation "${relation.id ?? relationKey}"`,
severity: "warning",
path: diagram.path,
field: "relations"
});
continue;
}
const sourceId = getObjectId2(sourceObject);
const targetId = getObjectId2(targetObject);
if (!presentObjectIds.has(sourceId) || !presentObjectIds.has(targetId)) {
continue;
}
seenRelationIds.add(relationKey);
edges.push(toClassDiagramEdge(relation, sourceObject, targetObject));
}
}
return edges;
}
function toDiagramEdge(relation, sourceObject, targetObject) {
return {
id: relation.id,
source: getObjectId2(sourceObject),
target: getObjectId2(targetObject),
kind: relation.kind,
label: relation.label,
metadata: {
sourceCardinality: relation.sourceCardinality,
targetCardinality: relation.targetCardinality
}
};
}
function toClassDiagramEdge(relation, sourceObject, targetObject) {
return {
id: relation.id,
source: getObjectId2(sourceObject),
target: getObjectId2(targetObject),
kind: relation.kind,
label: relation.label,
metadata: {
notes: relation.notes,
sourceCardinality: relation.fromMultiplicity,
targetCardinality: relation.toMultiplicity
}
};
}
function pushClassRelationTargetNotDiagramCompatibleWarnings(warnings, path2, endpoints) {
for (const endpoint of endpoints) {
if (endpoint.object || !endpoint.identity?.resolvedModel) {
continue;
}
warnings.push({
code: "class-relation-target-not-diagram-compatible",
message: formatClassRelationTargetNotDiagramCompatibleMessage2(
getReferenceDiagnosticLabel2(endpoint.reference, endpoint.identity)
),
severity: "warning",
path: path2,
field: "relations"
});
}
}
function getReferenceDiagnosticLabel2(reference, identity) {
return identity?.resolvedId ?? identity?.target ?? parseReferenceValue(reference)?.target ?? reference.trim();
}
function formatClassRelationTargetNotDiagramCompatibleMessage2(target) {
return `class relation target "${target}" exists, but is not compatible with Class Diagram rendering and was excluded. Consider representing non-structural cross-model relationships with Mapping.`;
}
function buildRelationKey2(relation) {
return `${relation.source}:${relation.kind}:${relation.target}:${relation.label ?? ""}`;
}
function buildClassRelationKey2(relation) {
return relation.id ?? `${relation.sourceClass}:${relation.targetClass}:${relation.kind}:${relation.label ?? ""}`;
}
function resolveErEdges(diagram, index, presentEntities, warnings) {
const edges = [];
const seenRelationIds = /* @__PURE__ */ new Set();
const presentEntityIds = new Set(presentEntities.map((entity) => entity.id));
const presentEntityKeys = /* @__PURE__ */ new Set();
for (const entity of presentEntities) {
for (const key of buildErEntityCanonicalKeys(entity)) {
presentEntityKeys.add(key);
}
}
for (const entity of presentEntities) {
for (const relation of entity.outboundRelations) {
const relationId = relation.id ?? `${entity.id}:${relation.targetEntity}:${relation.kind}`;
if (seenRelationIds.has(relationId)) {
continue;
}
seenRelationIds.add(relationId);
const targetEntity = resolveErEntityReference(relation.targetEntity, index);
if (!targetEntity) {
warnings.push({
code: "unresolved-reference",
message: `unresolved ER relation endpoint in relation "${relation.id ?? relationId}"`,
severity: "warning",
path: diagram.path,
field: "relations"
});
continue;
}
const targetIsPresent = presentEntityIds.has(targetEntity.id) || buildErEntityCanonicalKeys(targetEntity).some((key) => presentEntityKeys.has(key));
if (!targetIsPresent) {
continue;
}
edges.push(toErDiagramEdge(entity, targetEntity, relation));
}
}
return edges;
}
function getObjectId2(object) {
const explicitId = object.frontmatter.id;
if (typeof explicitId === "string" && explicitId.trim()) {
return explicitId.trim();
}
return object.name;
}
function getClassDiagramNodeDisplayName(reference, object) {
if (object) {
return object.name || getObjectId2(object);
}
const parsed = parseReferenceValue(reference);
if (parsed?.target) {
return parsed.target.split("/").pop() ?? parsed.target;
}
return parsed?.display || parsed?.raw || reference.trim();
}
function getErDiagramNodeDisplayName(reference, entity) {
const parsed = parseReferenceValue(reference);
if (parsed?.display) {
return parsed.display;
}
if (entity) {
return entity.logicalName || entity.physicalName || entity.id;
}
if (parsed?.target) {
return parsed.target.split("/").pop() ?? parsed.target;
}
return parsed?.raw || reference.trim();
}
function buildErEntityCanonicalKeys(entity) {
const keys = /* @__PURE__ */ new Set();
if (entity.id?.trim()) {
keys.add(`id:${entity.id.trim()}`);
}
if (entity.physicalName?.trim()) {
keys.add(`physical:${entity.physicalName.trim()}`);
}
if (entity.path?.trim()) {
const normalizedPath = entity.path.replace(/\\/g, "/").replace(/\.md$/i, "");
keys.add(`path:${normalizedPath}`);
const basename = normalizedPath.split("/").pop();
if (basename) {
keys.add(`basename:${basename}`);
}
}
return Array.from(keys);
}
function getDfdDiagramNodeDisplayName(entry, object) {
if (entry.label?.trim()) {
return entry.label.trim();
}
if (object) {
return object.name || object.id;
}
if (entry.id?.trim()) {
return entry.id.trim();
}
const reference = entry.ref?.trim() ?? "";
const parsed = parseReferenceValue(reference);
if (parsed?.target) {
return parsed.target.split("/").pop() ?? parsed.target;
}
return parsed?.raw || reference.trim();
}
function toErDiagramEdge(sourceEntity, targetEntity, relation) {
const mappingSummary = relation.mappings.map((mapping) => `${mapping.localColumn} -> ${mapping.targetColumn}`).join(" / ");
return {
id: relation.id,
source: sourceEntity.id,
target: targetEntity.id,
kind: "association",
label: relation.label,
metadata: {
cardinality: relation.cardinality,
sourceColumn: relation.mappings[0]?.localColumn,
targetColumn: relation.mappings[0]?.targetColumn,
logicalName: relation.label,
physicalName: relation.id,
kind: relation.kind,
mappingSummary,
mappings: relation.mappings
}
};
}
function createDfdFlowShapeWarning(path2, context, shape) {
return {
code: "invalid-structure",
message: `DFD flow shape "${shape}" may be unusual`,
severity: "warning",
path: path2,
field: "Flows",
context
};
}
// src/core/preview-routing.ts
function isDfdLikeDiagramPreviewFileType(fileType) {
return fileType === "dfd-diagram" || fileType === "flow-diagram";
}
function isDfdLikeDiagramPreviewModel(model) {
return isDfdLikeDiagramPreviewFileType(model.fileType);
}
// src/core/render-mode.ts
var VALID_RENDER_MODES = /* @__PURE__ */ new Set([
"custom",
"mermaid",
"mermaid-detail"
]);
var VALID_DOMAIN_RENDER_MODES = /* @__PURE__ */ new Set([
"mindmap",
"area",
"tree"
]);
var TABLE_TEXT_FORMATS = /* @__PURE__ */ new Set([
"data-object",
"app-process",
"rule",
"codeset",
"message",
"mapping"
]);
function resolveRenderMode(input) {
const diagnostics = [];
const toolbarMode = normalizeRenderModeForFormat(
input.toolbarOverride,
input.formatType
);
const frontmatterMode = normalizeFrontmatterRenderMode(
input.frontmatterRenderMode,
input.formatType,
input.filePath,
diagnostics
);
const settingsMode = normalizeRenderModeForFormat(
input.settingsDefaultRenderMode,
input.formatType
);
const formatDefaultMode = getFormatDefaultRenderMode(input.formatType);
const supportedModes = getSupportedRenderModes(input.formatType, input.modelKind);
const fallbackMode = getFallbackRenderMode(input.formatType, input.modelKind);
const toolbarResult = selectSupportedRenderMode(
toolbarMode,
"toolbar",
supportedModes
);
if (toolbarResult) {
return buildResolvedRenderMode(input, toolbarResult.mode, toolbarResult.source, diagnostics);
}
const frontmatterResult = selectSupportedRenderMode(
frontmatterMode,
"frontmatter",
supportedModes
);
if (frontmatterResult) {
return buildResolvedRenderMode(
input,
frontmatterResult.mode,
frontmatterResult.source,
diagnostics
);
}
if (frontmatterMode) {
diagnostics.push(
createRenderModeWarning(
input.filePath,
`${capitalizeRenderMode(frontmatterMode)} renderer is not supported for ${input.formatType}. Using the format default renderer.`,
"render_mode"
)
);
}
const settingsResult = selectSupportedRenderMode(
settingsMode,
"settings",
supportedModes
);
if (settingsResult) {
return buildResolvedRenderMode(input, settingsResult.mode, settingsResult.source, diagnostics);
}
const defaultResult = selectSupportedRenderMode(
formatDefaultMode,
"format_default",
supportedModes
);
if (defaultResult) {
return buildResolvedRenderMode(input, defaultResult.mode, defaultResult.source, diagnostics);
}
return buildResolvedRenderMode(
input,
fallbackMode,
"fallback",
diagnostics,
settingsMode ? `unsupported:${settingsMode}` : void 0
);
}
function getFormatDefaultRenderMode(formatType) {
switch (formatType) {
case "dfd-diagram":
case "flow-diagram":
return "mermaid";
case "domains":
case "domain-diagram":
return "mindmap";
default:
return "custom";
}
}
function getSupportedRenderModes(formatType, modelKind) {
return getForcedRenderModes(formatType, modelKind);
}
function getForcedRenderModes(formatType, modelKind) {
switch (formatType) {
case "domains":
case "domain-diagram":
return ["mindmap", "area", "tree"];
case "diagram":
if (modelKind === "class") {
return ["custom", "mermaid", "mermaid-detail"];
}
return modelKind === "er" ? ["custom", "mermaid", "mermaid-detail"] : ["custom"];
case "object":
return ["custom", "mermaid", "mermaid-detail"];
case "er-entity":
return ["custom", "mermaid", "mermaid-detail"];
case "dfd-diagram":
case "flow-diagram":
return ["mermaid"];
case "dfd-object":
return [];
case "screen":
return [];
case "data-object":
case "app-process":
case "rule":
case "codeset":
case "message":
case "mapping":
return [];
case "markdown":
return [];
default:
return ["custom"];
}
}
function normalizeRenderMode(value) {
if (typeof value !== "string") {
return null;
}
const normalized = value.trim().toLowerCase();
return VALID_RENDER_MODES.has(normalized) ? normalized : null;
}
function normalizeDomainRenderMode(value) {
if (typeof value !== "string") {
return null;
}
const normalized = value.trim().toLowerCase();
return VALID_DOMAIN_RENDER_MODES.has(normalized) ? normalized : null;
}
function normalizeRenderModeForFormat(value, formatType) {
if (formatType === "domains" || formatType === "domain-diagram") {
return normalizeDomainRenderMode(value);
}
return normalizeRenderMode(value);
}
function normalizeFrontmatterRenderMode(value, formatType, filePath, diagnostics) {
if (typeof value !== "string") {
return null;
}
const normalized = value.trim().toLowerCase();
if (!normalized) {
return null;
}
if (normalized === "auto") {
diagnostics.push(
createRenderModeWarning(
filePath,
'Deprecated render_mode value "auto". Using the format default renderer.',
"render_mode"
)
);
return null;
}
const mode = normalizeRenderModeForFormat(normalized, formatType);
if (!mode) {
diagnostics.push(
createRenderModeWarning(
filePath,
`Unknown render_mode value "${value}". Using the format default renderer.`,
"render_mode"
)
);
}
return mode;
}
function selectSupportedRenderMode(mode, source, supportedModes) {
if (!mode || !supportedModes.includes(mode)) {
return null;
}
return { mode, source };
}
function buildResolvedRenderMode(input, mode, source, diagnostics, fallbackReason) {
return {
selectedMode: mode,
effectiveMode: mode,
actualRenderer: getRendererImplementation(
input.formatType,
mode,
input.modelKind
),
source,
fallbackReason,
diagnostics: appendReducedOverviewNote(
diagnostics,
input.formatType,
input.modelKind,
mode,
input.filePath
)
};
}
function getFallbackRenderMode(formatType, modelKind) {
const supported = getForcedRenderModes(formatType, modelKind);
if (supported.includes(getFormatDefaultRenderMode(formatType))) {
return getFormatDefaultRenderMode(formatType);
}
return supported[0] ?? "custom";
}
function getRendererImplementation(formatType, mode, modelKind) {
if (formatType === "domains" || formatType === "domain-diagram") {
return "mermaid";
}
if ((mode === "mermaid" || mode === "mermaid-detail") && (formatType === "dfd-diagram" || formatType === "flow-diagram" || formatType === "object" || formatType === "er-entity" || formatType === "diagram" && (modelKind === "class" || modelKind === "er"))) {
return "mermaid";
}
if (TABLE_TEXT_FORMATS.has(formatType)) {
return "table-text";
}
return "custom";
}
function createRenderModeWarning(filePath, message, field) {
return {
code: "invalid-structure",
message,
severity: "warning",
filePath,
field,
section: "frontmatter"
};
}
function capitalizeRenderMode(value) {
return value.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
}
function appendReducedOverviewNote(diagnostics, formatType, modelKind, effectiveMode, filePath) {
if (effectiveMode !== "mermaid" || !(formatType === "object" || formatType === "er-entity" || formatType === "diagram" && (modelKind === "class" || modelKind === "er"))) {
return diagnostics;
}
return [
...diagnostics,
{
code: "invalid-structure",
message: "Mermaid mode shows reduced overview only.",
severity: "info",
filePath,
section: "frontmatter",
field: "render_mode"
}
];
}
// src/core/schema-detector.ts
var SCHEMA_TO_FILE_TYPE = {
model_object_v1: "object",
model_relations_v1: "relations"
};
var TYPE_TO_FILE_TYPE = {
class: "object",
data_object: "data-object",
app_process: "app-process",
screen: "screen",
rule: "rule",
codeset: "codeset",
message: "message",
mapping: "mapping",
color_scheme: "color-scheme",
domains: "domains",
domain_diagram: "domain-diagram",
dfd_object: "dfd-object",
dfd_diagram: "dfd-diagram",
flow_diagram: "flow-diagram",
"flow-diagram": "flow-diagram",
er_entity: "er-entity",
er_diagram: "diagram",
class_diagram: "diagram"
};
function detectFileType(value) {
const schema = typeof value === "string" ? value : value?.schema;
if (!schema) {
if (typeof value !== "string") {
const type = typeof value?.type === "string" ? value.type.trim() : "";
if (type && TYPE_TO_FILE_TYPE[type]) {
return TYPE_TO_FILE_TYPE[type];
}
}
return "markdown";
}
return SCHEMA_TO_FILE_TYPE[schema] ?? "markdown";
}
// src/core/supported-formats.ts
var SUPPORTED_MODEL_WEAVE_FORMATS = [
"class",
"class_diagram",
"er_entity",
"er_diagram",
"dfd_object",
"dfd_diagram",
"flow_diagram",
"data_object",
"app_process",
"screen",
"rule",
"codeset",
"message",
"mapping",
"color_scheme",
"domains",
"domain_diagram"
];
var SUPPORTED_MODEL_WEAVE_FORMAT_LIST = SUPPORTED_MODEL_WEAVE_FORMATS.join(" / ");
var SUPPORTED_MODEL_WEAVE_FILE_TYPES = /* @__PURE__ */ new Set([
"object",
"er-entity",
"diagram",
"dfd-object",
"dfd-diagram",
"flow-diagram",
"data-object",
"app-process",
"screen",
"rule",
"codeset",
"message",
"mapping",
"color-scheme",
"domains",
"domain-diagram"
]);
function isModelWeavePreviewSupportedFileType(fileType) {
return SUPPORTED_MODEL_WEAVE_FILE_TYPES.has(fileType);
}
// src/editor/model-weave-editor-suggest.ts
var import_obsidian2 = require("obsidian");
// src/core/internal-edge-adapters.ts
function toClassRelationEdge(relation, sourceClass = relation.source, targetClass = relation.target) {
return {
domain: "class",
id: relation.id,
source: sourceClass,
target: targetClass,
sourceClass,
targetClass,
kind: relation.kind,
label: relation.label,
notes: relation.description,
fromMultiplicity: relation.sourceCardinality,
toMultiplicity: relation.targetCardinality
};
}
function erRelationBlockToInternalEdge(relationBlock, sourceEntity) {
const sourceName = typeof sourceEntity === "string" ? sourceEntity : sourceEntity.id;
return {
domain: "er",
id: relationBlock.id,
source: sourceName,
target: relationBlock.targetTable ?? "",
sourceEntity: sourceName,
targetEntity: relationBlock.targetTable ?? "",
kind: relationBlock.kind ?? "fk",
label: relationBlock.id,
notes: relationBlock.notes ?? void 0,
cardinality: relationBlock.cardinality ?? void 0,
mappings: relationBlock.mappings.map((mapping) => ({
localColumn: mapping.localColumn,
targetColumn: mapping.targetColumn,
notes: mapping.notes ?? void 0
}))
};
}
function classDiagramEdgeToInternalEdge(edge) {
return {
domain: "class",
id: edge.id,
source: edge.source,
target: edge.target,
sourceClass: edge.source,
targetClass: edge.target,
kind: edge.kind ?? "association",
label: edge.label,
notes: typeof edge.metadata?.notes === "string" ? edge.metadata.notes : void 0,
fromMultiplicity: typeof edge.metadata?.sourceCardinality === "string" ? edge.metadata.sourceCardinality : void 0,
toMultiplicity: typeof edge.metadata?.targetCardinality === "string" ? edge.metadata.targetCardinality : void 0
};
}
function erDiagramEdgeToInternalEdge(edge) {
const mappings = [];
if (Array.isArray(edge.metadata?.mappings)) {
for (const mapping of edge.metadata.mappings) {
if (!mapping || typeof mapping !== "object") {
continue;
}
const candidate = mapping;
const localColumn = candidate.localColumn;
const targetColumn = candidate.targetColumn;
if (typeof localColumn !== "string" || typeof targetColumn !== "string") {
continue;
}
mappings.push({
localColumn,
targetColumn,
notes: typeof candidate.notes === "string" ? candidate.notes : void 0
});
}
}
return {
domain: "er",
id: edge.id,
source: edge.source,
target: edge.target,
sourceEntity: edge.source,
targetEntity: edge.target,
kind: edge.kind ?? "association",
label: getErEdgeLabel(edge),
notes: typeof edge.metadata?.notes === "string" ? edge.metadata.notes : void 0,
cardinality: typeof edge.metadata?.cardinality === "string" ? edge.metadata.cardinality : void 0,
mappings: mappings.length > 0 ? mappings : hasColumnMapping(edge) ? [
{
localColumn: String(edge.metadata?.sourceColumn),
targetColumn: String(edge.metadata?.targetColumn),
notes: typeof edge.metadata?.mappingNotes === "string" ? edge.metadata.mappingNotes : void 0
}
] : []
};
}
function getErEdgeLabel(edge) {
if (typeof edge.metadata?.logicalName === "string") {
return edge.metadata.logicalName;
}
if (typeof edge.metadata?.physicalName === "string") {
return edge.metadata.physicalName;
}
return edge.label;
}
function hasColumnMapping(edge) {
return typeof edge.metadata?.sourceColumn === "string" && typeof edge.metadata?.targetColumn === "string";
}
// src/parsers/er-entity-parser.ts
var COLUMN_HEADERS = [
"logical_name",
"physical_name",
"data_type",
"length",
"scale",
"not_null",
"pk",
"encrypted",
"default_value",
"notes"
];
var INDEX_HEADERS = [
"index_name",
"index_type",
"unique",
"columns",
"notes"
];
var RELATION_MAPPING_HEADERS = [
"local_column",
"target_column",
"notes"
];
function parseErEntityFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const warnings = [...frontmatterResult.warnings];
const frontmatter = frontmatterResult.file.frontmatter ?? {};
if (detectFileType(frontmatter) !== "er-entity") {
warnings.push(
createWarning4(
"invalid-structure",
'ER entity parser expected frontmatter type "er_entity"',
path2,
"type"
)
);
return { file: null, warnings };
}
const body = frontmatterResult.file.body;
const sections = extractMarkdownSections(body);
const id = getRequiredString(frontmatter, "id", warnings, path2);
const logicalName = getRequiredString(frontmatter, "logical_name", warnings, path2);
const physicalName = getRequiredString(frontmatter, "physical_name", warnings, path2);
if (!sections.Columns) {
warnings.push(
createInfoWarning("section-missing", 'section missing: "Columns"', path2, "Columns")
);
}
const columnTable = parseMarkdownTable(
sections.Columns,
[...COLUMN_HEADERS],
path2,
"Columns"
);
const indexTable = parseMarkdownTable(
sections.Indexes,
[...INDEX_HEADERS],
path2,
"Indexes"
);
warnings.push(...columnTable.warnings, ...indexTable.warnings);
const columns = columnTable.rows.map((row) => toErColumn(row, warnings, path2));
const indexes = indexTable.rows.map((row) => toErIndex(row));
const relationBlocks = parseRelationBlocks(body, warnings, path2);
const fallbackId = id || getFileStem2(path2) || "UNTITLED-ER-ENTITY";
const fallbackLogicalName = logicalName || physicalName || fallbackId;
const fallbackPhysicalName = physicalName || logicalName || fallbackId;
const baseEntity = {
fileType: "er-entity",
path: path2,
filePath: path2,
title: buildTitle(fallbackLogicalName, fallbackPhysicalName),
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id: fallbackId,
logicalName: fallbackLogicalName,
physicalName: fallbackPhysicalName,
schemaName: getOptionalString(frontmatter, "schema_name"),
dbms: getOptionalString(frontmatter, "dbms"),
columns,
indexes,
relationBlocks,
outboundRelations: []
};
baseEntity.outboundRelations = relationBlocks.map(
(relationBlock) => erRelationBlockToInternalEdge(relationBlock, baseEntity)
);
return {
file: baseEntity,
warnings
};
}
function getFileStem2(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function parseRelationBlocks(body, warnings, path2) {
const lines = body.replace(/\r\n/g, "\n").split("\n");
const relationsSectionLines = extractRelationsSectionLines(lines);
if (relationsSectionLines.length === 0) {
return [];
}
const blocks = [];
let currentId = null;
let currentLines = [];
const seenIds = /* @__PURE__ */ new Set();
const flushBlock = () => {
if (!currentId) {
return;
}
if (isIncompleteErRelationId2(currentId)) {
warnings.push(
createWarning4(
"invalid-structure",
`ER relation id looks incomplete: ${currentId}`,
path2,
"Relations"
)
);
}
if (seenIds.has(currentId)) {
warnings.push(
createWarning4(
"invalid-structure",
`duplicate ER relation id: ${currentId}`,
path2,
"Relations"
)
);
} else {
seenIds.add(currentId);
}
blocks.push(parseRelationBlock(currentId, currentLines, warnings, path2));
};
for (const line of relationsSectionLines) {
const trimmed = line.trim();
const match = trimmed.match(/^###\s+(.+)$/);
if (match) {
flushBlock();
currentId = match[1].trim();
currentLines = [];
continue;
}
if (currentId) {
currentLines.push(line);
}
}
flushBlock();
return blocks;
}
function extractRelationsSectionLines(lines) {
let inRelations = false;
const collected = [];
for (const line of lines) {
const trimmed = line.trim();
if (!inRelations) {
if (trimmed === "## Relations") {
inRelations = true;
}
continue;
}
if (/^##\s+/.test(trimmed)) {
break;
}
collected.push(line);
}
return collected;
}
function isIncompleteErRelationId2(id) {
const trimmed = id.trim();
if (!trimmed) {
return true;
}
const normalized = trimmed.toUpperCase();
return normalized === "REL" || normalized === "REL-" || normalized === "REL--" || normalized === "REL-NEW" || normalized === "REL-TODO";
}
function parseRelationBlock(id, lines, warnings, path2) {
const metadata = {};
const tableLines = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const metadataMatch = trimmed.match(/^-\s+([a-zA-Z_]+)\s*:\s*(.+)$/);
if (metadataMatch) {
metadata[metadataMatch[1]] = metadataMatch[2].trim();
continue;
}
if (trimmed.startsWith("|")) {
tableLines.push(trimmed);
}
}
const targetTableRaw = metadata.target_table?.trim() ?? null;
const targetTable = targetTableRaw ? normalizeReferenceTarget(targetTableRaw) : null;
const kind = metadata.kind?.trim() ?? null;
const cardinality = metadata.cardinality?.trim() ?? null;
const notes = metadata.notes?.trim() ?? null;
if (!targetTableRaw) {
warnings.push(
createWarning4(
"invalid-structure",
`relation block "${id}" missing required field "target_table"`,
path2,
"Relations"
)
);
}
const mappingTable = parseMarkdownTable(
tableLines,
[...RELATION_MAPPING_HEADERS],
path2,
`Relations:${id}`
);
warnings.push(...mappingTable.warnings);
const mappings = mappingTable.rows.map((row) => toRelationMapping(row));
return {
id,
targetTable,
kind,
cardinality,
notes,
mappings
};
}
function toRelationMapping(row) {
return {
localColumn: row.local_column ?? "",
targetColumn: row.target_column ?? "",
notes: toNullableString(row.notes)
};
}
function toErColumn(row, warnings, path2) {
return {
logicalName: row.logical_name ?? "",
physicalName: row.physical_name ?? "",
dataType: row.data_type ?? "",
length: parseNullableNumber(row.length, warnings, path2, "length"),
scale: parseNullableNumber(row.scale, warnings, path2, "scale"),
notNull: parseYN(row.not_null),
pk: parseYN(row.pk),
encrypted: parseYN(row.encrypted),
defaultValue: toNullableString(row.default_value),
notes: toNullableString(row.notes)
};
}
function toErIndex(row) {
return {
indexName: row.index_name ?? "",
indexType: row.index_type ?? "",
unique: parseYN(row.unique),
columns: row.columns ?? "",
notes: toNullableString(row.notes)
};
}
function parseNullableNumber(value, warnings, path2, field) {
const normalized = toNullableString(value);
if (normalized === null) {
return null;
}
const parsed = Number(normalized);
if (Number.isFinite(parsed)) {
return parsed;
}
warnings.push(
createWarning4(
"invalid-numeric-value",
`failed to parse numeric value "${normalized}" for "${field}"`,
path2,
field
)
);
return null;
}
function parseYN(value) {
return (value ?? "").trim().toUpperCase() === "Y";
}
function buildTitle(logicalName, physicalName) {
return `${logicalName} / ${physicalName}`;
}
function getRequiredString(frontmatter, key, warnings, path2) {
const value = getOptionalString(frontmatter, key);
if (value) {
return value;
}
warnings.push(
createWarning4(
key === "id" ? "missing-name" : "invalid-structure",
`missing required field "${key}"`,
path2,
key
)
);
return null;
}
function getOptionalString(frontmatter, key) {
const value = frontmatter[key];
return typeof value === "string" && value.trim() ? value.trim() : null;
}
function toNullableString(value) {
const normalized = value?.trim() ?? "";
return normalized ? normalized : null;
}
function createWarning4(code, message, path2, field) {
return {
code,
message,
severity: "warning",
path: path2,
field
};
}
function createInfoWarning(code, message, path2, field) {
return {
code,
message,
severity: "info",
path: path2,
field
};
}
// src/editor/model-weave-editor-suggest.ts
var NO_COMPLETION_NOTICE = modelWeaveText(
"No Model Weave completion is available at the current cursor position.",
"\u73FE\u5728\u306E\u30AB\u30FC\u30BD\u30EB\u4F4D\u7F6E\u3067\u5229\u7528\u3067\u304D\u308B Model Weave \u88DC\u5B8C\u306F\u3042\u308A\u307E\u305B\u3093\u3002"
);
var MARKDOWN_ONLY_NOTICE = modelWeaveText(
"Model Weave completion is available only in Markdown editors.",
"Model Weave \u88DC\u5B8C\u306F Markdown \u30A8\u30C7\u30A3\u30BF\u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002"
);
var TARGET_TABLE_NOT_RESOLVED_NOTICE = modelWeaveText(
"Target table is not resolved for the current relation block.",
"\u73FE\u5728\u306E relation block \u306E target_table \u304C\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002"
);
var CLASS_RELATION_KIND_OPTIONS = [
"association",
"dependency",
"inheritance",
"implementation",
"aggregation",
"composition"
];
var SCREEN_ACTION_KIND_OPTIONS = [
"ui_action",
"field_event",
"screen_event",
"form_event",
"system_event",
"shortcut",
"auto",
"other"
];
var SCREEN_ACTION_EVENT_OPTIONS = [
"load",
"unload",
"click",
"change",
"input",
"focus",
"blur",
"submit",
"select",
"keydown",
"timer",
"message",
"other"
];
var SCREEN_TYPE_OPTIONS = [
"entry",
"list",
"detail",
"confirm",
"complete",
"dialog",
"dashboard",
"admin",
"other"
];
var SCREEN_LAYOUT_KIND_OPTIONS = [
"header",
"body",
"detail",
"footer",
"section",
"form_area",
"table_area",
"action_area",
"search_area",
"result_area",
"message_area",
"other"
];
var SCREEN_FIELD_KIND_OPTIONS = [
"window",
"form",
"panel",
"section",
"table",
"list",
"input",
"textarea",
"select",
"checkbox",
"radio",
"button",
"link",
"label",
"hidden",
"computed",
"table_input",
"table_select",
"other"
];
var SCREEN_FIELD_DATA_TYPE_OPTIONS = [
"string",
"number",
"integer",
"decimal",
"boolean",
"date",
"datetime",
"time",
"array",
"object",
"binary",
"other"
];
var SCREEN_REQUIRED_OPTIONS = ["Y", "N"];
var SCREEN_MESSAGE_SEVERITY_OPTIONS = [
"info",
"success",
"warning",
"error",
"confirm",
"other"
];
var DATA_OBJECT_KIND_OPTIONS = [
"data",
"dto",
"request",
"response",
"payload",
"file",
"form",
"query",
"result",
"report",
"message",
"other"
];
var DATA_OBJECT_FORMAT_OPTIONS = [
"object",
"json",
"xml",
"csv",
"tsv",
"fixed",
"delimited",
"excel",
"edi",
"binary",
"form",
"query",
"other"
];
var DATA_OBJECT_ENCODING_OPTIONS = ["UTF-8", "Shift_JIS", "EUC-JP", "ISO-8859-1"];
var DATA_OBJECT_LINE_ENDING_OPTIONS = ["LF", "CRLF", "CR"];
var DATA_OBJECT_HAS_HEADER_OPTIONS = ["true", "false"];
var DATA_OBJECT_FORMAT_KEY_OPTIONS = [
"data_format",
"encoding",
"delimiter",
"quote",
"escape",
"line_ending",
"has_header",
"record_length",
"record_type_position",
"padding",
"numeric_padding",
"sheet",
"template"
];
var DATA_OBJECT_FORMAT_VALUE_OPTIONS = {
data_format: DATA_OBJECT_FORMAT_OPTIONS,
encoding: DATA_OBJECT_ENCODING_OPTIONS,
line_ending: DATA_OBJECT_LINE_ENDING_OPTIONS,
has_header: DATA_OBJECT_HAS_HEADER_OPTIONS,
padding: ["space", "zero", "none"],
numeric_padding: ["zero", "space", "none"],
quote: ["double_quote", "single_quote", "none"],
escape: ["backslash", "double_quote", "none"]
};
var DATA_OBJECT_RECORD_OCCURRENCE_OPTIONS = ["1", "0..1", "1..*", "0..*"];
var DATA_OBJECT_REQUIRED_OPTIONS = ["Y", "N"];
var DATA_OBJECT_FIELD_TYPE_OPTIONS = [
"string",
"number",
"integer",
"decimal",
"boolean",
"date",
"datetime",
"time",
"array",
"object",
"binary",
"other"
];
var DATA_OBJECT_FIELD_FORMAT_OPTIONS = [
"yyyyMMdd",
"yyyy/MM/dd",
"zero_pad_left",
"space_pad_right",
"fixed:",
"decimal_0",
"decimal_2",
"half_width",
"half_width_kana",
"full_width"
];
var CODESET_KIND_OPTIONS = [
"enum",
"status",
"master_code",
"system_code",
"external_code",
"ui_options",
"other"
];
function openModelWeaveCompletion(app, getIndex) {
const activeView = app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
const file = activeView?.file ?? null;
const editor = activeView?.editor;
if (!file || file.extension !== "md" || !editor) {
new import_obsidian2.Notice(MARKDOWN_ONLY_NOTICE);
return;
}
const request = resolveCompletionRequest(file, editor, getIndex());
if ("notice" in request) {
new import_obsidian2.Notice(request.notice);
return;
}
if (request.suggestions.length === 0) {
new import_obsidian2.Notice(NO_COMPLETION_NOTICE);
return;
}
const modal = new ModelWeaveCompletionModal(app, editor, request);
modal.open();
modal.applyInitialQuery();
}
var ModelWeaveCompletionModal = class extends import_obsidian2.FuzzySuggestModal {
constructor(app, editor, request) {
super(app);
this.editor = editor;
this.request = request;
this.setPlaceholder(request.placeholder);
this.emptyStateText = "No matching Model Weave candidates.";
}
getItems() {
return this.request.suggestions;
}
getItemText(item) {
return [item.label, item.insertText, item.resolveKey, item.detail].filter((value) => Boolean(value)).join(" ");
}
renderSuggestion(item, el) {
const suggestion = item.item;
el.createDiv({ text: suggestion.label });
if (suggestion.detail) {
el.createDiv({
text: suggestion.detail,
cls: "model-weave-editor-suggest-detail"
});
}
}
onChooseItem(item) {
const liveEditor = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView)?.editor ?? this.editor;
const cursor = replaceSuggestionText(liveEditor, this.request, item);
restoreCompletionCursor(liveEditor, cursor);
}
applyInitialQuery() {
if (!this.request.initialQuery) {
return;
}
this.inputEl.value = this.request.initialQuery;
this.inputEl.dispatchEvent(new Event("input"));
}
};
function resolveCompletionRequest(file, editor, index) {
const content = editor.getValue();
const type = getFrontmatterType(content);
if (!type) {
return { notice: NO_COMPLETION_NOTICE };
}
const cursor = editor.getCursor();
const lines = content.split(/\r?\n/);
const line = lines[cursor.line] ?? "";
if (type === "er_entity") {
const targetTableRequest = getTargetTableCompletion(cursor, line, index);
if (targetTableRequest) {
return targetTableRequest;
}
const mappingRequest = getErMappingCompletion(file, content, lines, cursor, line, index);
if (mappingRequest) {
return mappingRequest;
}
}
if (type === "er_diagram" || type === "class_diagram") {
if (type === "class_diagram") {
const relationPickerRequest = getClassDiagramRelationsCompletion(
lines,
cursor,
line,
content,
index
);
if (relationPickerRequest) {
return relationPickerRequest;
}
}
const request = getDiagramObjectsRefCompletion(lines, cursor, line, type, index);
if (request) {
return request;
}
}
if (type === "dfd_diagram") {
const objectRequest = getDfdDiagramObjectsRefCompletion(
lines,
cursor,
line,
index
);
if (objectRequest) {
return objectRequest;
}
const flowRequest = getDfdDiagramFlowCompletion(lines, cursor, line, index);
if (flowRequest) {
return flowRequest;
}
}
if (type === "data_object") {
const request = getDataObjectCompletion(content, lines, cursor, line, index);
if (request) {
return request;
}
}
if (type === "screen") {
const request = getScreenCompletion(content, lines, cursor, line, index);
if (request) {
return request;
}
}
if (type === "app_process") {
const request = getAppProcessCompletion(lines, cursor, line, index);
if (request) {
return request;
}
}
if (type === "rule") {
const request = getRuleCompletion(lines, cursor, line, index);
if (request) {
return request;
}
}
if (type === "mapping") {
const request = getMappingCompletion(lines, cursor, line, index);
if (request) {
return request;
}
}
if (type === "codeset") {
const request = getCodeSetCompletion(content, lines, cursor, line);
if (request) {
return request;
}
}
if (type === "class") {
const request = getClassRelationsCompletion(lines, cursor, line, index);
if (request) {
return request;
}
}
return { notice: NO_COMPLETION_NOTICE };
}
function getTargetTableCompletion(cursor, line, index) {
const match = line.match(/^(\s*-\s*target_table\s*:\s*)(.*)$/);
if (!match) {
return null;
}
const prefixLength = match[1].length;
if (cursor.ch < prefixLength || !index) {
return null;
}
const suggestions = Object.values(index.erEntitiesById).sort((left, right) => left.physicalName.localeCompare(right.physicalName)).map((entity) => toErEntitySuggestion(entity));
return {
kind: "er-target-table",
replaceFrom: { line: cursor.line, ch: prefixLength },
replaceTo: { line: cursor.line, ch: line.length },
suggestions,
placeholder: "Complete ER target_table",
initialQuery: normalizeCompletionQuery(line.slice(prefixLength))
};
}
function getErMappingCompletion(file, content, lines, cursor, line, index) {
if (!line.trim().startsWith("|")) {
return null;
}
if (getSectionNameAtLine(lines, cursor.line) !== "Relations") {
return null;
}
const tableHeaderIndex = findNearestLine(lines, cursor.line, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length >= 3 && row[0] === "local_column" && row[1] === "target_column" && row[2] === "notes";
});
if (tableHeaderIndex < 0 || cursor.line <= tableHeaderIndex + 1) {
return null;
}
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || cell.columnIndex > 1) {
return null;
}
const currentEntity = parseErEntityFile(content, file.path).file;
if (!currentEntity) {
return { notice: NO_COMPLETION_NOTICE };
}
if (cell.columnIndex === 0) {
return {
kind: "er-local-column",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: currentEntity.columns.map((column) => column.physicalName).filter((value) => Boolean(value)).filter(onlyUnique).sort().map((physicalName) => ({
label: physicalName,
insertText: physicalName,
resolveKey: physicalName,
kind: "column"
})),
placeholder: "Complete local column",
initialQuery: extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
};
}
const targetTableRef = findCurrentRelationTargetTable(lines, cursor.line);
if (!targetTableRef || !index) {
return { notice: TARGET_TABLE_NOT_RESOLVED_NOTICE };
}
const targetEntity = resolveErEntityReference(targetTableRef, index);
if (!targetEntity) {
return { notice: TARGET_TABLE_NOT_RESOLVED_NOTICE };
}
return {
kind: "er-target-column",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: targetEntity.columns.map((column) => column.physicalName).filter((value) => Boolean(value)).filter(onlyUnique).sort().map((physicalName) => ({
label: physicalName,
insertText: physicalName,
resolveKey: physicalName,
kind: "column"
})),
placeholder: "Complete target column",
initialQuery: extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
};
}
function getClassDiagramRelationsCompletion(lines, cursor, line, content, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
if (getSectionNameAtLine(lines, cursor.line) !== "Relations") {
return null;
}
const tableHeaderIndex = findNearestLine(lines, cursor.line, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length >= 8 && row[0] === "id" && row[1] === "from" && row[2] === "to" && row[3] === "kind";
});
if (tableHeaderIndex < 0 || cursor.line <= tableHeaderIndex + 1) {
return null;
}
if (isMarkdownTableSeparator(line)) {
return null;
}
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell) {
return null;
}
const suggestions = getClassDiagramRelationSuggestions(content, index);
if (suggestions.length === 0) {
return {
notice: modelWeaveText(
"No class relations are available for the current diagram.",
"\u73FE\u5728\u306E diagram \u3067\u5229\u7528\u3067\u304D\u308B class relation \u306F\u3042\u308A\u307E\u305B\u3093\u3002"
)
};
}
return {
kind: "class-diagram-relation-picker",
replaceFrom: { line: cursor.line, ch: 0 },
replaceTo: { line: cursor.line, ch: line.length },
suggestions,
placeholder: "Pick a class relation for this diagram row"
};
}
function getDiagramObjectsRefCompletion(lines, cursor, line, type, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
if (getSectionNameAtLine(lines, cursor.line) !== "Objects") {
return null;
}
const tableHeaderIndex = findNearestLine(lines, cursor.line, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length >= 2 && row[0] === "ref" && row[1] === "notes";
});
if (tableHeaderIndex < 0 || cursor.line <= tableHeaderIndex + 1) {
return null;
}
if (isMarkdownTableSeparator(line)) {
return null;
}
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || cell.columnIndex !== 0) {
return null;
}
if (type === "er_diagram") {
return {
kind: "er-diagram-object",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: Object.values(index.erEntitiesById).sort((left, right) => left.physicalName.localeCompare(right.physicalName)).map((entity) => toErEntitySuggestion(entity)),
placeholder: "Complete ER diagram object",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 0
};
}
return {
kind: "class-diagram-object",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: Object.values(index.objectsById).sort((left, right) => getObjectId3(left).localeCompare(getObjectId3(right))).map((object) => toClassObjectSuggestion(object)),
placeholder: "Complete class diagram object",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 0
};
}
function getDfdDiagramObjectsRefCompletion(lines, cursor, line, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
if (getSectionNameAtLine(lines, cursor.line) !== "Objects") {
return null;
}
const tableHeaderIndex = findNearestLine(lines, cursor.line, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length >= 2 && row[0] === "ref" && row[1] === "notes";
});
if (tableHeaderIndex < 0 || cursor.line <= tableHeaderIndex + 1) {
return null;
}
if (isMarkdownTableSeparator(line)) {
return null;
}
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || cell.columnIndex !== 0) {
return null;
}
return {
kind: "dfd-diagram-object",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: Object.values(index.dfdObjectsById).sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDfdObjectSuggestion(object)),
placeholder: "Complete DFD diagram object",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 0
};
}
function getDfdDiagramFlowCompletion(lines, cursor, line, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
if (getSectionNameAtLine(lines, cursor.line) !== "Flows") {
return null;
}
const tableHeaderIndex = findNearestLine(lines, cursor.line, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length >= 5 && row[0] === "id" && row[1] === "from" && row[2] === "to";
});
if (tableHeaderIndex < 0 || cursor.line <= tableHeaderIndex + 1) {
return null;
}
if (isMarkdownTableSeparator(line)) {
return null;
}
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || cell.columnIndex !== 1 && cell.columnIndex !== 2 && cell.columnIndex !== 3) {
return null;
}
if (cell.columnIndex === 3) {
return {
kind: "dfd-diagram-flow-data",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: Object.values(index.dataObjectsById).sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDataObjectSuggestion(object)),
placeholder: "Complete DFD flow data",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 3
};
}
const preferredObjects = getDiagramObjectRefs(lines.join("\n")).map((ref) => resolveDfdObjectReference(ref, index)).filter((object) => Boolean(object));
const preferredIds = new Set(preferredObjects.map((object) => object.id));
const remainingObjects = Object.values(index.dfdObjectsById).filter(
(object) => !preferredIds.has(object.id)
);
const orderedSuggestions = [
...preferredObjects.sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDfdObjectSuggestion(object, "in diagram")),
...remainingObjects.sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDfdObjectSuggestion(object, "in vault"))
];
return {
kind: cell.columnIndex === 1 ? "dfd-diagram-flow-from" : "dfd-diagram-flow-to",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: orderedSuggestions,
placeholder: cell.columnIndex === 1 ? "Complete DFD flow source" : "Complete DFD flow target",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: cell.columnIndex
};
}
function getDataObjectCompletion(content, lines, cursor, line, index) {
const frontmatterRequest = getDataObjectFrontmatterCompletion(content, cursor, line);
if (frontmatterRequest) {
return frontmatterRequest;
}
if (!line.trim().startsWith("|") || !index || isMarkdownTableSeparator(line)) {
return null;
}
const section = getSectionNameAtLine(lines, cursor.line);
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell) {
return null;
}
if (section === "Format") {
if (!hasTableHeader(lines, cursor.line, ["key", "value", "notes"])) {
return null;
}
if (cell.columnIndex === 0) {
return buildOptionCompletionRequest(
"data-object-option",
cell,
line,
DATA_OBJECT_FORMAT_KEY_OPTIONS,
"Complete data object format key",
0
);
}
if (cell.columnIndex === 1) {
const row = parseMarkdownTableRow(line);
const key = row?.[0]?.trim() ?? "";
const options = DATA_OBJECT_FORMAT_VALUE_OPTIONS[key];
if (!options || options.length === 0) {
return null;
}
return buildOptionCompletionRequest(
"data-object-option",
cell,
line,
options,
`Complete data object format value for ${key}`,
1
);
}
}
if (section === "Records") {
if (!hasTableHeader(lines, cursor.line, ["record_type", "name", "occurrence", "notes"])) {
return null;
}
if (cell.columnIndex === 2) {
return buildOptionCompletionRequest(
"data-object-option",
cell,
line,
DATA_OBJECT_RECORD_OCCURRENCE_OPTIONS,
"Complete data object occurrence",
2
);
}
}
if (section === "Fields") {
const header = getNearestTableHeader(lines, cursor.line);
if (!header) {
return null;
}
const headerIndex = new Map(header.map((column, index2) => [column, index2]));
const isFileLayout = headerIndex.has("record_type") || headerIndex.has("no") || headerIndex.has("position") || headerIndex.has("field_format");
const refColumnIndex = headerIndex.get("ref");
if (typeof refColumnIndex === "number" && cell.columnIndex === refColumnIndex) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
"Complete data object field reference"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
return {
kind: "data-object-field-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildDataObjectReferenceSuggestions(index),
placeholder: "Complete data object field reference",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: refColumnIndex
};
}
const requiredColumnIndex = headerIndex.get("required");
if (typeof requiredColumnIndex === "number" && cell.columnIndex === requiredColumnIndex) {
return buildOptionCompletionRequest(
"data-object-option",
cell,
line,
DATA_OBJECT_REQUIRED_OPTIONS,
"Complete data object required flag",
requiredColumnIndex
);
}
const typeColumnIndex = headerIndex.get("type");
if (typeof typeColumnIndex === "number" && cell.columnIndex === typeColumnIndex) {
return buildOptionCompletionRequest(
"data-object-option",
cell,
line,
DATA_OBJECT_FIELD_TYPE_OPTIONS,
"Complete data object field type",
typeColumnIndex
);
}
const fieldFormatColumnIndex = headerIndex.get("field_format");
if (isFileLayout && typeof fieldFormatColumnIndex === "number" && cell.columnIndex === fieldFormatColumnIndex) {
return buildOptionCompletionRequest(
"data-object-option",
cell,
line,
DATA_OBJECT_FIELD_FORMAT_OPTIONS,
"Complete data object field format",
fieldFormatColumnIndex
);
}
}
return null;
}
function getScreenCompletion(content, lines, cursor, line, index) {
const frontmatterRequest = getScreenFrontmatterCompletion(content, lines, cursor, line);
if (frontmatterRequest) {
return frontmatterRequest;
}
if (!line.trim().startsWith("|") || !index) {
return null;
}
const section = getSectionNameAtLine(lines, cursor.line);
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || isMarkdownTableSeparator(line)) {
return null;
}
if (section === "Layout") {
if (!hasTableHeader(lines, cursor.line, ["id", "label", "kind", "purpose", "notes"])) {
return null;
}
if (cell.columnIndex === 2) {
return buildOptionCompletionRequest(
"screen-option",
cell,
line,
SCREEN_LAYOUT_KIND_OPTIONS,
"Complete screen layout kind",
2
);
}
}
if (section === "Fields") {
if (!hasTableHeader(lines, cursor.line, [
"id",
"label",
"kind",
"layout",
"data_type",
"required",
"ref",
"rule",
"notes"
])) {
return null;
}
if (cell.columnIndex === 2) {
return buildOptionCompletionRequest(
"screen-field-kind",
cell,
line,
SCREEN_FIELD_KIND_OPTIONS,
"Complete screen field kind",
2
);
}
if (cell.columnIndex === 3) {
return {
kind: "screen-field-layout",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: getScreenLayoutSuggestions(lines),
placeholder: "Complete screen layout",
initialQuery: extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch),
tableColumnIndex: 3
};
}
if (cell.columnIndex === 4) {
return buildOptionCompletionRequest(
"screen-field-data-type",
cell,
line,
SCREEN_FIELD_DATA_TYPE_OPTIONS,
"Complete screen field data type",
4
);
}
if (cell.columnIndex === 5) {
return buildOptionCompletionRequest(
"screen-field-required",
cell,
line,
SCREEN_REQUIRED_OPTIONS,
"Complete screen field required flag",
5
);
}
if (cell.columnIndex === 6) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
"Complete screen field reference"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
return {
kind: "screen-field-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildStructuredReferenceSuggestions(index),
placeholder: "Complete screen field ref",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: 6
};
}
if (cell.columnIndex === 7) {
return {
kind: "screen-rule-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildRuleReferenceSuggestions(index),
placeholder: "Complete screen field rule",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 7
};
}
}
if (section === "Actions") {
if (!hasTableHeader(lines, cursor.line, [
"id",
"label",
"kind",
"target",
"event",
"invoke",
"transition",
"rule",
"notes"
])) {
return null;
}
if (cell.columnIndex === 2) {
return buildOptionCompletionRequest(
"screen-action-kind",
cell,
line,
SCREEN_ACTION_KIND_OPTIONS,
"Complete screen action kind",
2
);
}
if (cell.columnIndex === 3) {
return {
kind: "screen-action-target",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: getScreenFieldTargetSuggestions(lines),
placeholder: "Complete screen action target",
initialQuery: extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch),
tableColumnIndex: 3
};
}
if (cell.columnIndex === 4) {
return buildOptionCompletionRequest(
"screen-action-event",
cell,
line,
SCREEN_ACTION_EVENT_OPTIONS,
"Complete screen action event",
4
);
}
if (cell.columnIndex === 5) {
const appProcessSuggestions = Object.values(index.appProcessesById).sort((left, right) => left.id.localeCompare(right.id)).map((process) => toAppProcessSuggestion(process));
return {
kind: "screen-action-invoke",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: [...getScreenLocalProcessSuggestions(lines), ...appProcessSuggestions],
placeholder: "Complete screen invoke reference",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 5
};
}
if (cell.columnIndex === 6) {
return {
kind: "screen-action-transition",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: Object.values(index.screensById).sort((left, right) => left.id.localeCompare(right.id)).map((screen) => toScreenSuggestion(screen)),
placeholder: "Complete screen transition reference",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 6
};
}
if (cell.columnIndex === 7) {
return {
kind: "screen-rule-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildRuleReferenceSuggestions(index),
placeholder: "Complete screen action rule",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 7
};
}
}
if (section === "Messages") {
if (!hasTableHeader(lines, cursor.line, ["id", "text", "severity", "timing", "notes"])) {
return null;
}
if (cell.columnIndex === 1) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
return {
kind: "rule-message-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: Object.values(index.messagesById).sort((left, right) => left.id.localeCompare(right.id)).map((messageSet) => toMessageSuggestion(messageSet)),
placeholder: "Complete screen message reference",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: 1
};
}
if (cell.columnIndex === 2) {
return buildOptionCompletionRequest(
"screen-message-severity",
cell,
line,
SCREEN_MESSAGE_SEVERITY_OPTIONS,
"Complete message severity",
2
);
}
}
return null;
}
function getScreenFrontmatterCompletion(content, lines, cursor, line) {
if (!isLineInsideFrontmatter(content, cursor.line)) {
return null;
}
const frontmatterKey = getFrontmatterKeyAtLine(line);
if (frontmatterKey !== "screen_type") {
return null;
}
const separatorIndex = line.indexOf(":");
if (separatorIndex < 0) {
return null;
}
return {
kind: "screen-frontmatter",
replaceFrom: { line: cursor.line, ch: separatorIndex + 1 },
replaceTo: { line: cursor.line, ch: lines[cursor.line]?.length ?? line.length },
suggestions: SCREEN_TYPE_OPTIONS.map((option) => ({
label: option,
insertText: option,
resolveKey: option,
kind: "kind"
})),
placeholder: "Complete screen screen_type",
initialQuery: line.slice(separatorIndex + 1).trim()
};
}
function getScreenLayoutSuggestions(lines) {
const layouts = /* @__PURE__ */ new Map();
let inLayout = false;
let headerSeen = false;
for (const rawLine of lines) {
const trimmed = rawLine.trim();
const headingMatch = trimmed.match(/^##\s+(.+)$/);
if (headingMatch) {
inLayout = headingMatch[1].trim() === "Layout";
headerSeen = false;
continue;
}
if (!inLayout || !trimmed.startsWith("|") || isMarkdownTableSeparator(trimmed)) {
continue;
}
const row = parseMarkdownTableRow(trimmed);
if (!row || row.length < 2) {
continue;
}
if (!headerSeen) {
headerSeen = row[0] === "id" && row[1] === "label";
continue;
}
const id = row[0]?.trim();
const label = row[1]?.trim();
if (id) {
layouts.set(id, label || id);
}
}
return [...layouts.entries()].map(([id, label]) => ({
label: `${id} / ${label}`,
insertText: id,
resolveKey: id,
detail: "screen layout",
kind: "reference"
}));
}
function getScreenLocalProcessSuggestions(lines) {
const suggestions = [];
let inLocalProcesses = false;
for (const rawLine of lines) {
const trimmed = rawLine.trim();
const headingMatch = trimmed.match(/^##\s+(.+)$/);
if (headingMatch) {
inLocalProcesses = headingMatch[1].trim() === "Local Processes";
continue;
}
if (!inLocalProcesses) {
continue;
}
const localProcessMatch = trimmed.match(/^###\s+(.+)$/);
if (!localProcessMatch) {
continue;
}
const heading = localProcessMatch[1].trim();
suggestions.push({
label: heading,
insertText: buildAliasedWikilink(`#${heading}`, heading),
resolveKey: `#${heading}`,
detail: "screen local process",
kind: "reference"
});
}
return suggestions;
}
function getAppProcessCompletion(lines, cursor, line, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
const section = getSectionNameAtLine(lines, cursor.line);
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || isMarkdownTableSeparator(line)) {
return null;
}
if (section === "Inputs") {
if (!hasTableHeader(lines, cursor.line, ["id", "data", "source", "required", "notes"])) {
return null;
}
if (cell.columnIndex === 1 || cell.columnIndex === 2) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
if (cell.columnIndex === 2) {
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
"Complete app_process input source"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
}
return {
kind: cell.columnIndex === 1 ? "app-process-input-data" : "app-process-input-source",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildStructuredReferenceSuggestions(index),
placeholder: cell.columnIndex === 1 ? "Complete app_process input data" : "Complete app_process input source",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: cell.columnIndex
};
}
}
if (section === "Outputs") {
if (!hasTableHeader(lines, cursor.line, ["id", "data", "target", "notes"])) {
return null;
}
if (cell.columnIndex === 1 || cell.columnIndex === 2) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
if (cell.columnIndex === 2) {
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
"Complete app_process output target"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
}
return {
kind: cell.columnIndex === 1 ? "app-process-output-data" : "app-process-output-target",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildStructuredReferenceSuggestions(index),
placeholder: cell.columnIndex === 1 ? "Complete app_process output data" : "Complete app_process output target",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: cell.columnIndex
};
}
}
if (section === "Triggers") {
if (!hasTableHeader(lines, cursor.line, ["id", "kind", "source", "event", "notes"])) {
return null;
}
if (cell.columnIndex === 2) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
"Complete app_process trigger source"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
return {
kind: "app-process-trigger-source",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildStructuredReferenceSuggestions(index),
placeholder: "Complete app_process trigger source",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: 2
};
}
}
if (section === "Transitions") {
if (!hasTableHeader(lines, cursor.line, ["id", "event", "to", "condition", "notes"])) {
return null;
}
if (cell.columnIndex === 2) {
return {
kind: "app-process-transition-to",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: Object.values(index.screensById).sort((left, right) => left.id.localeCompare(right.id)).map((screen) => toScreenSuggestion(screen)),
placeholder: "Complete transition screen reference",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: 2
};
}
}
return null;
}
function getRuleCompletion(lines, cursor, line, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
const section = getSectionNameAtLine(lines, cursor.line);
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || isMarkdownTableSeparator(line)) {
return null;
}
if (section === "Inputs") {
if (!hasTableHeader(lines, cursor.line, ["id", "data", "source", "required", "notes"])) {
return null;
}
if (cell.columnIndex === 1 || cell.columnIndex === 2) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
cell.columnIndex === 1 ? "Complete rule input data" : "Complete rule input source"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
return {
kind: cell.columnIndex === 1 ? "rule-input-data" : "rule-input-source",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildStructuredReferenceSuggestions(index),
placeholder: cell.columnIndex === 1 ? "Complete rule input data" : "Complete rule input source",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: cell.columnIndex
};
}
}
if (section === "References") {
if (!hasTableHeader(lines, cursor.line, ["ref", "usage", "notes"])) {
return null;
}
if (cell.columnIndex === 0) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
"Complete rule reference"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
return {
kind: "rule-reference-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildRuleReferenceableSuggestions(index),
placeholder: "Complete rule reference",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: 0
};
}
}
if (section === "Messages") {
if (!hasTableHeader(lines, cursor.line, ["condition", "message", "severity", "notes"])) {
return null;
}
if (cell.columnIndex === 2) {
return buildOptionCompletionRequest(
"rule-message-ref",
cell,
line,
["error", "warning", "info", "confirm"],
"Complete rule message severity",
2
);
}
if (cell.columnIndex === 1) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
return {
kind: "rule-message-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildGenericFileSuggestions(index),
placeholder: "Complete rule message reference",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: 1
};
}
}
return null;
}
function getMappingCompletion(lines, cursor, line, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
const section = getSectionNameAtLine(lines, cursor.line);
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || isMarkdownTableSeparator(line)) {
return null;
}
if (section === "Scope") {
if (!hasTableHeader(lines, cursor.line, ["role", "ref", "notes"])) {
return null;
}
if (cell.columnIndex === 0) {
return buildOptionCompletionRequest(
"mapping-scope-ref",
cell,
line,
["source", "target", "intermediate", "reference", "rule", "process"],
"Complete mapping scope role",
0
);
}
if (cell.columnIndex === 1) {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
"Complete mapping scope ref"
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
return {
kind: "mapping-scope-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildRuleReferenceableSuggestions(index),
placeholder: "Complete mapping scope ref",
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: 1
};
}
}
if (section === "Mappings") {
const mappingHeader = getNearestSupportedMappingHeader(lines, cursor.line);
if (!mappingHeader) {
return null;
}
const columnName = mappingHeader[cell.columnIndex];
if (columnName === "source_ref" || columnName === "target_ref") {
const cellValue = extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch);
const isSourceRef = columnName === "source_ref";
const placeholder = isSourceRef ? "Complete mapping source_ref" : "Complete mapping target_ref";
const qualifiedMemberRequest = getQualifiedMemberCompletionRequest(
cursor,
cell,
cellValue,
index,
placeholder
);
if (qualifiedMemberRequest) {
return qualifiedMemberRequest;
}
return {
kind: isSourceRef ? "mapping-source-ref" : "mapping-target-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: buildStructuredReferenceSuggestions(index),
placeholder,
initialQuery: normalizeCompletionQuery(cellValue),
tableColumnIndex: cell.columnIndex
};
}
if (columnName === "rule") {
return {
kind: "mapping-rule-ref",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: [
...buildRuleReferenceSuggestions(index),
...Object.values(index.codesetsById).sort((left, right) => left.id.localeCompare(right.id)).map((codeset) => toCodeSetSuggestion(codeset))
],
placeholder: "Complete mapping rule",
initialQuery: normalizeCompletionQuery(
extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch)
),
tableColumnIndex: cell.columnIndex
};
}
if (columnName === "required") {
return buildOptionCompletionRequest(
"mapping-rule-ref",
cell,
line,
["Y", "N"],
"Complete mapping required",
cell.columnIndex
);
}
}
return null;
}
function getCodeSetCompletion(content, lines, cursor, line) {
const frontmatterRequest = getCodeSetFrontmatterCompletion(content, cursor, line);
if (frontmatterRequest) {
return frontmatterRequest;
}
if (!line.trim().startsWith("|")) {
return null;
}
const section = getSectionNameAtLine(lines, cursor.line);
if (section !== "Values") {
return null;
}
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell || isMarkdownTableSeparator(line)) {
return null;
}
if (!hasTableHeader(lines, cursor.line, ["code", "label", "sort_order", "active", "notes"])) {
return null;
}
if (cell.columnIndex === 3) {
return buildOptionCompletionRequest(
"codeset-active",
cell,
line,
["Y", "N"],
"Complete codeset active",
3
);
}
return null;
}
function getCodeSetFrontmatterCompletion(content, cursor, line) {
if (!isLineInsideFrontmatter(content, cursor.line)) {
return null;
}
const frontmatterKey = getFrontmatterKeyAtLine(line);
if (frontmatterKey !== "kind") {
return null;
}
const separatorIndex = line.indexOf(":");
if (separatorIndex < 0) {
return null;
}
return {
kind: "codeset-active",
replaceFrom: { line: cursor.line, ch: separatorIndex + 1 },
replaceTo: { line: cursor.line, ch: line.length },
suggestions: CODESET_KIND_OPTIONS.map((option) => ({
label: option,
insertText: option,
resolveKey: option,
kind: "kind"
})),
placeholder: "Complete codeset kind",
initialQuery: line.slice(separatorIndex + 1).trim()
};
}
function buildStructuredReferenceSuggestions(index) {
return [
...Object.values(index.dataObjectsById).sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDataObjectSuggestion(object)),
...Object.values(index.codesetsById).sort((left, right) => left.id.localeCompare(right.id)).map((codeset) => toCodeSetSuggestion(codeset)),
...Object.values(index.rulesById).sort((left, right) => left.id.localeCompare(right.id)).map((rule) => toRuleSuggestion(rule)),
...Object.values(index.mappingsById).sort((left, right) => left.id.localeCompare(right.id)).map((mapping) => toMappingSuggestion(mapping)),
...Object.values(index.screensById).sort((left, right) => left.id.localeCompare(right.id)).map((screen) => toScreenSuggestion(screen)),
...Object.values(index.appProcessesById).sort((left, right) => left.id.localeCompare(right.id)).map((process) => toAppProcessSuggestion(process)),
...Object.values(index.erEntitiesById).sort((left, right) => left.logicalName.localeCompare(right.logicalName)).map((entity) => toLinkedReferenceSuggestionForEntity(entity)),
...Object.values(index.objectsById).sort((left, right) => getObjectId3(left).localeCompare(getObjectId3(right))).map((object) => toLinkedReferenceSuggestionForClass(object))
];
}
function buildRuleReferenceSuggestions(index) {
return Object.values(index.rulesById).sort((left, right) => left.id.localeCompare(right.id)).map((rule) => toRuleSuggestion(rule));
}
function buildRuleReferenceableSuggestions(index) {
return [
...buildRuleReferenceSuggestions(index),
...Object.values(index.codesetsById).sort((left, right) => left.id.localeCompare(right.id)).map((codeset) => toCodeSetSuggestion(codeset)),
...Object.values(index.messagesById).sort((left, right) => left.id.localeCompare(right.id)).map((messageSet) => toMessageSuggestion(messageSet)),
...Object.values(index.dataObjectsById).sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDataObjectSuggestion(object)),
...Object.values(index.screensById).sort((left, right) => left.id.localeCompare(right.id)).map((screen) => toScreenSuggestion(screen)),
...Object.values(index.appProcessesById).sort((left, right) => left.id.localeCompare(right.id)).map((process) => toAppProcessSuggestion(process)),
...Object.values(index.mappingsById).sort((left, right) => left.id.localeCompare(right.id)).map((mapping) => toMappingSuggestion(mapping)),
...Object.values(index.erEntitiesById).sort((left, right) => left.logicalName.localeCompare(right.logicalName)).map((entity) => toLinkedReferenceSuggestionForEntity(entity)),
...Object.values(index.objectsById).sort((left, right) => getObjectId3(left).localeCompare(getObjectId3(right))).map((object) => toLinkedReferenceSuggestionForClass(object))
];
}
function buildDataObjectReferenceSuggestions(index) {
return [
...Object.values(index.erEntitiesById).sort((left, right) => left.logicalName.localeCompare(right.logicalName)).map((entity) => toLinkedReferenceSuggestionForEntity(entity)),
...Object.values(index.objectsById).sort((left, right) => getObjectId3(left).localeCompare(getObjectId3(right))).map((object) => toLinkedReferenceSuggestionForClass(object)),
...Object.values(index.dataObjectsById).sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDataObjectSuggestion(object)),
...Object.values(index.codesetsById).sort((left, right) => left.id.localeCompare(right.id)).map((codeset) => toCodeSetSuggestion(codeset)),
...Object.values(index.screensById).sort((left, right) => left.id.localeCompare(right.id)).map((screen) => toScreenSuggestion(screen)),
...Object.values(index.appProcessesById).sort((left, right) => left.id.localeCompare(right.id)).map((process) => toAppProcessSuggestion(process)),
...Object.values(index.dfdObjectsById).sort((left, right) => left.id.localeCompare(right.id)).map((object) => toDfdObjectSuggestion(object))
];
}
function buildGenericFileSuggestions(index) {
return buildRuleReferenceableSuggestions(index);
}
function getScreenFieldTargetSuggestions(lines) {
const fields = /* @__PURE__ */ new Map();
let inFields = false;
let headerSeen = false;
for (const rawLine of lines) {
const trimmed = rawLine.trim();
const headingMatch = trimmed.match(/^##\s+(.+)$/);
if (headingMatch) {
inFields = headingMatch[1].trim() === "Fields";
headerSeen = false;
continue;
}
if (!inFields || !trimmed.startsWith("|") || isMarkdownTableSeparator(trimmed)) {
continue;
}
const row = parseMarkdownTableRow(trimmed);
if (!row || row.length < 2) {
continue;
}
if (!headerSeen) {
headerSeen = row[0] === "id" && row[1] === "label";
continue;
}
const id = row[0]?.trim();
const label = row[1]?.trim();
if (id) {
fields.set(id, label || id);
}
}
return [...fields.entries()].map(([id, label]) => ({
label: `${id} / ${label}`,
insertText: id,
resolveKey: id,
detail: "screen field target",
kind: "reference"
}));
}
function getNearestSupportedMappingHeader(lines, cursorLine) {
const tableHeaderIndex = findNearestLine(lines, cursorLine, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return isSupportedMappingHeader(row);
});
if (tableHeaderIndex < 0 || cursorLine <= tableHeaderIndex + 1) {
return null;
}
return parseMarkdownTableRow(lines[tableHeaderIndex] ?? "");
}
function isSupportedMappingHeader(header) {
if (!header) {
return false;
}
return sameOrderedHeaders(header, ["target_ref", "source_ref", "transform", "rule", "required", "notes"]) || sameOrderedHeaders(header, ["source_ref", "target_ref", "transform", "rule", "required", "notes"]);
}
function sameOrderedHeaders(actual, expected) {
return actual.length === expected.length && expected.every((header, index) => actual[index] === header);
}
function hasTableHeader(lines, cursorLine, expectedHeader) {
const tableHeaderIndex = findNearestLine(lines, cursorLine, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && expectedHeader.every((header, index) => row[index] === header);
});
return tableHeaderIndex >= 0 && cursorLine > tableHeaderIndex + 1;
}
function getNearestTableHeader(lines, cursorLine) {
const tableHeaderIndex = findNearestLine(lines, cursorLine, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length > 0;
});
if (tableHeaderIndex < 0 || cursorLine <= tableHeaderIndex + 1) {
return null;
}
const header = parseMarkdownTableRow(lines[tableHeaderIndex] ?? "");
return header && header.length > 0 ? header : null;
}
function buildOptionCompletionRequest(kind, cell, line, options, placeholder, tableColumnIndex) {
return {
kind,
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: options.map((option) => ({
label: option,
insertText: option,
resolveKey: option,
kind: "kind"
})),
placeholder,
initialQuery: extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch),
tableColumnIndex
};
}
function getQualifiedMemberCompletionRequest(cursor, cell, cellValue, index, placeholder) {
const qualified = parseQualifiedRef(cellValue);
if (!qualified) {
return null;
}
const normalizedCellValue = cellValue.trim();
const dotIndex = normalizedCellValue.lastIndexOf(".");
if (dotIndex < 0) {
return null;
}
const memberStartInCell = normalizedCellValue.slice(0, dotIndex + 1).length;
const memberQuery = normalizedCellValue.slice(dotIndex + 1).trim();
const memberCandidates = getQualifiedMemberCandidates(qualified.baseRefRaw, index);
if (memberCandidates.length === 0) {
return null;
}
const rawCellStart = cell.replaceFrom.ch;
const rawTrimmedStart = lineTrimmedOffset(cellValue);
const replaceFromCh = rawCellStart + rawTrimmedStart + memberStartInCell;
const replaceToCh = cell.replaceTo.ch;
if (cursor.ch < replaceFromCh - 1) {
return null;
}
return {
kind: "data-object-field-ref",
replaceFrom: { line: cursor.line, ch: replaceFromCh },
replaceTo: { line: cursor.line, ch: replaceToCh },
suggestions: memberCandidates.sort((left, right) => left.memberId.localeCompare(right.memberId)).map((candidate) => ({
label: candidate.displayName ? `${candidate.memberId} \u2014 ${candidate.displayName}` : candidate.memberId,
insertText: candidate.memberId,
resolveKey: `${candidate.ownerId}.${candidate.memberId}`,
detail: `${candidate.memberKind} \xB7 ${candidate.sourceSection}`,
kind: "reference"
})),
placeholder,
initialQuery: memberQuery
};
}
function getClassDiagramRelationSuggestions(content, index) {
const objectRefs = getDiagramObjectRefs(content);
const diagramObjects = objectRefs.map((ref) => resolveObjectModelReference(ref, index)).filter((object) => Boolean(object));
const diagramObjectIds = new Set(diagramObjects.map((object) => getObjectId3(object)));
const seen = /* @__PURE__ */ new Set();
const suggestions = [];
for (const object of diagramObjects) {
for (const relation of object.relations) {
const targetObject = index.objectsById[relation.targetClass] ?? resolveObjectModelReference(relation.targetClass, index);
const key = [
relation.id ?? "",
relation.sourceClass,
relation.targetClass,
relation.kind,
relation.label ?? "",
relation.fromMultiplicity ?? "",
relation.toMultiplicity ?? ""
].join("|");
if (seen.has(key)) {
continue;
}
seen.add(key);
const insideDiagram = diagramObjectIds.has(relation.targetClass);
suggestions.push(
toClassDiagramRelationSuggestion(relation, object, targetObject, insideDiagram)
);
}
}
return suggestions.sort((left, right) => {
const leftPriority = left.detail?.includes("in diagram") ? 0 : 1;
const rightPriority = right.detail?.includes("in diagram") ? 0 : 1;
if (leftPriority !== rightPriority) {
return leftPriority - rightPriority;
}
return left.label.localeCompare(right.label);
});
}
function getClassRelationsCompletion(lines, cursor, line, index) {
if (!line.trim().startsWith("|") || !index) {
return null;
}
if (getSectionNameAtLine(lines, cursor.line) !== "Relations") {
return null;
}
const tableHeaderIndex = findNearestLine(lines, cursor.line, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length >= 7 && row[0] === "id" && row[1] === "to" && row[2] === "kind";
});
if (tableHeaderIndex < 0 || cursor.line <= tableHeaderIndex + 1) {
return null;
}
if (isMarkdownTableSeparator(line)) {
return null;
}
const cell = getTableCellContext(line, cursor.line, cursor.ch);
if (!cell) {
return null;
}
if (cell.columnIndex === 1) {
const suggestions = Object.values(index.objectsById).sort((left, right) => getObjectId3(left).localeCompare(getObjectId3(right))).map((object) => ({
label: `${getObjectId3(object)} \u2014 ${object.name}`,
insertText: buildAliasedWikilink(
toFileLinkTarget(object.path),
object.name || getObjectId3(object)
),
resolveKey: getObjectId3(object),
detail: object.kind,
kind: "class"
}));
return {
kind: "class-relation-to",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions,
placeholder: "Complete class relation to",
initialQuery: extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch),
tableColumnIndex: 1
};
}
if (cell.columnIndex === 2) {
return {
kind: "class-relation-kind",
replaceFrom: cell.replaceFrom,
replaceTo: cell.replaceTo,
suggestions: CLASS_RELATION_KIND_OPTIONS.map((kind) => ({
label: kind,
insertText: kind,
resolveKey: kind,
kind: "kind"
})),
placeholder: "Complete class relation kind",
initialQuery: extractLineText(line, cell.replaceFrom.ch, cell.replaceTo.ch),
tableColumnIndex: 2
};
}
return null;
}
function getFrontmatterType(content) {
const parsed = parseFrontmatter(content);
const type = parsed.file.frontmatter?.type;
return typeof type === "string" && type.trim() ? type.trim() : void 0;
}
function getDataObjectFrontmatterCompletion(content, cursor, line) {
if (!isLineInsideFrontmatter(content, cursor.line)) {
return null;
}
const frontmatterKey = getFrontmatterKeyAtLine(line);
if (!frontmatterKey) {
return null;
}
const optionsByKey = {
data_format: DATA_OBJECT_FORMAT_OPTIONS,
kind: DATA_OBJECT_KIND_OPTIONS,
encoding: DATA_OBJECT_ENCODING_OPTIONS,
line_ending: DATA_OBJECT_LINE_ENDING_OPTIONS,
has_header: DATA_OBJECT_HAS_HEADER_OPTIONS
};
const options = optionsByKey[frontmatterKey];
if (!options) {
return null;
}
const separatorIndex = line.indexOf(":");
if (separatorIndex < 0) {
return null;
}
const replaceFrom = { line: cursor.line, ch: separatorIndex + 1 };
const replaceTo = { line: cursor.line, ch: line.length };
return {
kind: "data-object-frontmatter",
replaceFrom,
replaceTo,
suggestions: options.map((option) => ({
label: option,
insertText: option,
resolveKey: option,
kind: "kind"
})),
placeholder: `Complete data_object ${frontmatterKey}`,
initialQuery: line.slice(separatorIndex + 1).trim()
};
}
function isLineInsideFrontmatter(content, lineIndex) {
const lines = content.split(/\r?\n/);
if ((lines[0] ?? "").trim() !== "---") {
return false;
}
for (let index = 1; index < lines.length; index += 1) {
if (lines[index].trim() === "---") {
return lineIndex > 0 && lineIndex < index;
}
}
return false;
}
function getFrontmatterKeyAtLine(line) {
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_-]*)\s*:/);
return match ? match[1] : null;
}
function getSectionNameAtLine(lines, lineIndex) {
for (let index = lineIndex; index >= 0; index -= 1) {
const trimmed = lines[index].trim();
const match = trimmed.match(/^##\s+(.+)$/);
if (match) {
return match[1].trim();
}
}
return null;
}
function findNearestLine(lines, startIndex, predicate) {
for (let index = startIndex; index >= 0; index -= 1) {
const candidate = lines[index] ?? "";
if (predicate(candidate)) {
return index;
}
if (index !== startIndex && /^##\s+/.test(candidate.trim())) {
break;
}
}
return -1;
}
function findCurrentRelationTargetTable(lines, startIndex) {
const blockStartIndex = findCurrentRelationBlockStart(lines, startIndex);
if (blockStartIndex < 0) {
return null;
}
for (let index = blockStartIndex + 1; index <= startIndex; index += 1) {
const trimmed = (lines[index] ?? "").trim();
if (/^###\s+/.test(trimmed)) {
break;
}
const match = trimmed.match(/^-\s*target_table\s*:\s*(.*)$/);
if (match) {
const value = match[1].trim();
return value ? normalizeReferenceTarget(value) : null;
}
}
return null;
}
function findCurrentRelationBlockStart(lines, startIndex) {
for (let index = startIndex; index >= 0; index -= 1) {
const trimmed = (lines[index] ?? "").trim();
if (/^###\s+/.test(trimmed)) {
return index;
}
if (/^##\s+/.test(trimmed) && index !== startIndex) {
break;
}
}
return -1;
}
function getTableCellContext(line, lineNumber, cursorCh) {
const ranges = getMarkdownTableCellRanges(line);
if (!ranges || ranges.length === 0) {
return null;
}
for (const range of ranges) {
const inCell = cursorCh >= range.rawStart && cursorCh < range.rawEnd || cursorCh === range.rawEnd || cursorCh === range.rawStart - 1 && range.columnIndex > 0;
if (!inCell) {
continue;
}
return {
columnIndex: range.columnIndex,
replaceFrom: { line: lineNumber, ch: range.contentStart },
replaceTo: { line: lineNumber, ch: range.contentEnd }
};
}
return null;
}
function parseMarkdownTableRow(line) {
return splitMarkdownTableRow(line);
}
function isMarkdownTableSeparator(line) {
const cells = parseMarkdownTableRow(line);
if (!cells || cells.length === 0) {
return false;
}
return cells.every((cell) => /^:?-{3,}:?$/.test(cell));
}
function getObjectId3(object) {
const explicitId = object.frontmatter.id;
if (typeof explicitId === "string" && explicitId.trim()) {
return explicitId.trim();
}
return object.name;
}
function onlyUnique(value, index, array) {
return array.indexOf(value) === index;
}
function toErEntitySuggestion(entity) {
const linkTarget = toFileLinkTarget(entity.path);
const displayName = entity.logicalName || entity.physicalName || entity.id;
return {
label: `${entity.logicalName} / ${entity.physicalName}`,
insertText: buildAliasedWikilink(linkTarget, displayName),
resolveKey: linkTarget,
detail: `${entity.id} \xB7 ${entity.path}`,
kind: "er_entity"
};
}
function toLinkedReferenceSuggestionForEntity(entity) {
const linkTarget = toFileLinkTarget(entity.path);
const displayName = entity.logicalName || entity.physicalName || entity.id;
return {
label: `${displayName} / ${entity.physicalName}`,
insertText: buildAliasedWikilink(linkTarget, displayName),
resolveKey: linkTarget,
detail: `er_entity \xB7 ${entity.id} \xB7 ${entity.path}`,
kind: "er_entity"
};
}
function toClassObjectSuggestion(object) {
const linkTarget = toFileLinkTarget(object.path);
const displayName = object.name || getObjectId3(object);
return {
label: `${getObjectId3(object)} / ${object.name}`,
insertText: buildAliasedWikilink(linkTarget, displayName),
resolveKey: linkTarget,
detail: object.kind,
kind: "class"
};
}
function toLinkedReferenceSuggestionForClass(object) {
const linkTarget = toFileLinkTarget(object.path);
const displayName = object.name || getObjectId3(object);
return {
label: `${displayName} / ${getObjectId3(object)}`,
insertText: buildAliasedWikilink(linkTarget, displayName),
resolveKey: linkTarget,
detail: `class \xB7 ${object.kind} \xB7 ${object.path}`,
kind: "class"
};
}
function toDfdObjectSuggestion(object, scopeDetail) {
const linkTarget = toFileLinkTarget(object.path);
return {
label: `${object.id} / ${object.name}`,
insertText: buildAliasedWikilink(linkTarget, object.name || object.id),
resolveKey: linkTarget,
detail: scopeDetail ? `${object.kind} \xB7 ${scopeDetail}` : object.kind,
kind: "dfd_object"
};
}
function toDataObjectSuggestion(object) {
const linkTarget = toFileLinkTarget(object.path);
return {
label: `${object.id} / ${object.name}`,
insertText: buildAliasedWikilink(linkTarget, object.name || object.id),
resolveKey: linkTarget,
detail: object.kind ?? "data_object",
kind: "data_object"
};
}
function toScreenSuggestion(screen) {
const linkTarget = toFileLinkTarget(screen.path);
return {
label: `${screen.id} / ${screen.name}`,
insertText: buildAliasedWikilink(linkTarget, screen.name || screen.id),
resolveKey: linkTarget,
detail: screen.screenType ?? "screen",
kind: "reference"
};
}
function toAppProcessSuggestion(process) {
const linkTarget = toFileLinkTarget(process.path);
return {
label: `${process.id} / ${process.name}`,
insertText: buildAliasedWikilink(linkTarget, process.name || process.id),
resolveKey: linkTarget,
detail: process.kind ?? "app_process",
kind: "reference"
};
}
function toCodeSetSuggestion(codeset) {
const linkTarget = toFileLinkTarget(codeset.path);
return {
label: `${codeset.id} / ${codeset.name}`,
insertText: buildAliasedWikilink(linkTarget, codeset.name || codeset.id),
resolveKey: linkTarget,
detail: codeset.kind ?? "codeset",
kind: "reference"
};
}
function toMessageSuggestion(messageSet) {
const linkTarget = toFileLinkTarget(messageSet.path);
return {
label: `${messageSet.id} / ${messageSet.name}`,
insertText: buildAliasedWikilink(linkTarget, messageSet.name || messageSet.id),
resolveKey: linkTarget,
detail: messageSet.kind ?? "message",
kind: "reference"
};
}
function toRuleSuggestion(rule) {
const linkTarget = toFileLinkTarget(rule.path);
return {
label: `${rule.id} / ${rule.name}`,
insertText: buildAliasedWikilink(linkTarget, rule.name || rule.id),
resolveKey: linkTarget,
detail: rule.kind ?? "rule",
kind: "reference"
};
}
function toMappingSuggestion(mapping) {
const linkTarget = toFileLinkTarget(mapping.path);
return {
label: `${mapping.id} / ${mapping.name}`,
insertText: buildAliasedWikilink(linkTarget, mapping.name || mapping.id),
resolveKey: linkTarget,
detail: mapping.kind ?? "mapping",
kind: "reference"
};
}
function toFileLinkTarget(path2) {
return path2.replace(/\\/g, "/").replace(/\.md$/i, "");
}
function normalizeCompletionQuery(value) {
const trimmed = value.trim();
const withoutOpening = trimmed.startsWith("[[") ? trimmed.slice(2) : trimmed;
const withoutClosing = withoutOpening.endsWith("]]") ? withoutOpening.slice(0, -2) : withoutOpening;
const normalized = withoutClosing.replace(/\\\|/g, "|");
let escaped = false;
for (let index = 0; index < normalized.length; index += 1) {
const char = normalized[index];
if (escaped) {
escaped = false;
continue;
}
if (char === "\\") {
escaped = true;
continue;
}
if (char === "|") {
return normalized.slice(0, index).trim();
}
}
return normalized.trim();
}
function lineTrimmedOffset(value) {
const match = value.match(/^\s*/);
return match ? match[0].length : 0;
}
function getDiagramObjectRefs(content) {
const lines = content.split(/\r?\n/);
const refs = [];
let inObjectsSection = false;
let headerSeen = false;
for (const rawLine of lines) {
const trimmed = rawLine.trim();
const headingMatch = trimmed.match(/^##\s+(.+)$/);
if (headingMatch) {
inObjectsSection = headingMatch[1].trim() === "Objects";
headerSeen = false;
continue;
}
if (!inObjectsSection || !trimmed.startsWith("|")) {
continue;
}
if (isMarkdownTableSeparator(trimmed)) {
continue;
}
const row = parseMarkdownTableRow(trimmed);
if (!row || row.length < 2) {
continue;
}
if (!headerSeen) {
headerSeen = row[0] === "ref" && row[1] === "notes";
continue;
}
if (row[0]) {
refs.push(row[0]);
}
}
return refs;
}
function extractLineText(line, from, to) {
return line.slice(from, to).trim();
}
function replaceSuggestionText(editor, request, suggestion) {
const insertText = suggestion.insertText;
if (request.tableColumnIndex !== void 0 && (request.kind === "er-diagram-object" || request.kind === "dfd-diagram-object" || request.kind === "dfd-diagram-flow-from" || request.kind === "dfd-diagram-flow-to" || request.kind === "dfd-diagram-flow-data" || request.kind === "data-object-field-ref" || request.kind === "data-object-option" || request.kind === "screen-option" || request.kind === "class-diagram-object" || request.kind === "class-relation-to" || request.kind === "class-relation-kind" || request.kind === "screen-field-ref" || request.kind === "screen-field-layout" || request.kind === "screen-field-kind" || request.kind === "screen-field-data-type" || request.kind === "screen-field-required" || request.kind === "screen-action-target" || request.kind === "screen-action-kind" || request.kind === "screen-action-event" || request.kind === "screen-action-invoke" || request.kind === "screen-action-transition" || request.kind === "screen-rule-ref" || request.kind === "screen-message-severity" || request.kind === "app-process-input-data" || request.kind === "app-process-input-source" || request.kind === "app-process-output-data" || request.kind === "app-process-output-target" || request.kind === "app-process-trigger-source" || request.kind === "app-process-transition-to" || request.kind === "rule-input-data" || request.kind === "rule-input-source" || request.kind === "rule-reference-ref" || request.kind === "rule-message-ref" || request.kind === "mapping-scope-ref" || request.kind === "mapping-source-ref" || request.kind === "mapping-target-ref" || request.kind === "mapping-rule-ref" || request.kind === "codeset-active")) {
return replaceMarkdownTableCell(editor, request, insertText);
}
if (request.kind === "class-diagram-relation-picker") {
if (suggestion.rowValues) {
return replaceClassDiagramRelationRow(
editor,
request.replaceFrom.line,
suggestion.rowValues
);
}
}
editor.replaceRange(insertText, request.replaceFrom, request.replaceTo);
return {
line: request.replaceFrom.line,
ch: request.replaceFrom.ch + insertText.length
};
}
function restoreCompletionCursor(editor, cursor) {
focusMarkdownEditor(editor);
editor.setSelection(cursor, cursor);
editor.setCursor(cursor);
const defer = typeof window !== "undefined" && typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame.bind(window) : (callback) => window.setTimeout(() => callback(0), 0);
defer(() => {
focusMarkdownEditor(editor);
editor.setSelection(cursor, cursor);
editor.setCursor(cursor);
});
}
function focusMarkdownEditor(editor) {
const editorWithFocus = editor;
editorWithFocus.focus?.();
editorWithFocus.cm?.focus?.();
}
function replaceClassDiagramRelationRow(editor, lineNumber, rowValues) {
const line = editor.getLine(lineNumber);
const existingCells = parseMarkdownTableRow(line) ?? [];
const cells = Array.from({ length: 8 }).fill("");
for (let index = 0; index < Math.min(existingCells.length, cells.length); index += 1) {
cells[index] = existingCells[index] ?? "";
}
const existingId = cells[0].trim();
if (!existingId) {
const preferredId = normalizeRelationId(rowValues.id) ?? buildFallbackRelationId(rowValues.from ?? "", rowValues.to ?? "", rowValues.kind ?? "");
cells[0] = ensureUniqueClassDiagramRelationId(editor, lineNumber, preferredId);
}
cells[1] = rowValues.from ?? cells[1];
cells[2] = rowValues.to ?? cells[2];
cells[3] = rowValues.kind ?? cells[3];
cells[4] = rowValues.label ?? cells[4];
cells[5] = rowValues.from_multiplicity ?? cells[5];
cells[6] = rowValues.to_multiplicity ?? cells[6];
const nextLine = `| ${cells.join(" | ")} |`;
editor.replaceRange(
nextLine,
{ line: lineNumber, ch: 0 },
{ line: lineNumber, ch: line.length }
);
return {
line: lineNumber,
ch: nextLine.length
};
}
function toClassDiagramRelationSuggestion(relation, sourceObject, targetObject, insideDiagram) {
const labelPart = relation.label ? ` | ${relation.label}` : "";
const multiplicityPart = relation.fromMultiplicity || relation.toMultiplicity ? ` [${relation.fromMultiplicity ?? ""} -> ${relation.toMultiplicity ?? ""}]` : "";
return {
label: `${relation.sourceClass} -> ${relation.targetClass} | ${relation.kind}${labelPart}${multiplicityPart}`,
insertText: relation.id ?? `${relation.sourceClass}->${relation.targetClass}`,
detail: insideDiagram ? "target in diagram" : "target outside diagram",
kind: "class",
rowValues: {
id: relation.id ?? "",
from: toObjectDiagramWikilink(sourceObject),
to: targetObject ? toObjectDiagramWikilink(targetObject) : toReferenceWikilink(relation.targetClass),
kind: relation.kind,
label: relation.label ?? "",
from_multiplicity: relation.fromMultiplicity ?? "",
to_multiplicity: relation.toMultiplicity ?? ""
}
};
}
function toObjectDiagramWikilink(object) {
const displayName = object.name || typeof object.frontmatter?.id === "string" && object.frontmatter.id.trim() || getFileStem3(object.path);
return buildAliasedWikilink(toFileLinkTarget(object.path), displayName);
}
function toReferenceWikilink(reference) {
return buildAliasedWikilink(normalizeReferenceTarget(reference), normalizeReferenceTarget(reference).split("/").pop() ?? normalizeReferenceTarget(reference));
}
function normalizeRelationId(value) {
const trimmed = value?.trim();
return trimmed ? trimmed : null;
}
function buildFallbackRelationId(from, to, kind) {
const source = sanitizeRelationIdPart(from) || "FROM";
const target = sanitizeRelationIdPart(to) || "TO";
const relationKind = sanitizeRelationIdPart(kind);
return relationKind ? `REL-${source}-${relationKind}-${target}` : `REL-${source}-${target}`;
}
function sanitizeRelationIdPart(value) {
return value.trim().replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toUpperCase();
}
function ensureUniqueClassDiagramRelationId(editor, lineNumber, preferredId) {
const existingIds = collectExistingClassDiagramRelationIds(editor, lineNumber);
if (!existingIds.has(preferredId)) {
return preferredId;
}
let suffix = 2;
while (existingIds.has(`${preferredId}-${suffix}`)) {
suffix += 1;
}
return `${preferredId}-${suffix}`;
}
function collectExistingClassDiagramRelationIds(editor, lineNumber) {
const lines = editor.getValue().split(/\r?\n/);
const headerRowIndex = findNearestLine(lines, lineNumber, (candidate) => {
const row = parseMarkdownTableRow(candidate);
return row !== null && row.length >= 8 && row[0] === "id" && row[1] === "from" && row[2] === "to" && row[3] === "kind";
});
if (headerRowIndex < 0) {
return /* @__PURE__ */ new Set();
}
const ids = /* @__PURE__ */ new Set();
for (let index = headerRowIndex + 2; index < lines.length; index += 1) {
const candidate = lines[index] ?? "";
const trimmed = candidate.trim();
if (/^##\s+/.test(trimmed)) {
break;
}
if (!trimmed.startsWith("|") || isMarkdownTableSeparator(trimmed)) {
continue;
}
if (index === lineNumber) {
continue;
}
const row = parseMarkdownTableRow(trimmed);
const id = row?.[0]?.trim();
if (id) {
ids.add(id);
}
}
return ids;
}
function replaceMarkdownTableCell(editor, request, insertText) {
const lineNumber = request.replaceFrom.line;
const line = editor.getLine(lineNumber);
const ranges = getMarkdownTableCellRanges(line);
const columnIndex = request.tableColumnIndex ?? 0;
if (!ranges || columnIndex >= ranges.length) {
editor.replaceRange(insertText, request.replaceFrom, request.replaceTo);
return {
line: request.replaceFrom.line,
ch: request.replaceFrom.ch + insertText.length
};
}
const range = ranges[columnIndex];
const nextLine = `${line.slice(0, range.rawStart)} ${insertText} ${line.slice(range.rawEnd)}`;
editor.replaceRange(
nextLine,
{ line: lineNumber, ch: 0 },
{ line: lineNumber, ch: line.length }
);
return {
line: lineNumber,
ch: range.rawStart + 1 + insertText.length
};
}
function buildAliasedWikilink(target, displayName) {
return `[[${target}\\|${escapeWikilinkAlias(displayName)}]]`;
}
function escapeWikilinkAlias(value) {
return value.replace(/\|/g, "\\|");
}
function getFileStem3(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? path2;
}
// src/export/png-export.ts
var import_obsidian4 = require("obsidian");
// src/adapters/obsidian-mermaid.ts
var import_obsidian3 = require("obsidian");
async function loadMermaidAdapter() {
const mermaid = await (0, import_obsidian3.loadMermaid)();
if (!isMermaidAdapter(mermaid)) {
throw new Error("Obsidian Mermaid adapter is unavailable.");
}
return mermaid;
}
function isMermaidAdapter(value) {
return typeof value === "object" && value !== null && "render" in value && typeof value.render === "function";
}
// src/i18n/localized-messages.ts
function formatMermaidRenderStatusMessage(status, language) {
const statusText = modelWeaveText(
status,
status === "generated" ? "\u751F\u6210\u6E08\u307F" : status === "rendered" ? "\u63CF\u753B\u6E08\u307F" : "\u5931\u6557",
language
);
return modelWeaveText(
`Render status: ${statusText}`,
`\u63CF\u753B\u30B9\u30C6\u30FC\u30BF\u30B9: ${statusText}`,
language
);
}
function formatMermaidRenderErrorMessage(error, language) {
return modelWeaveText(
`Render error: ${error}`,
`\u63CF\u753B\u30A8\u30E9\u30FC: ${error}`,
language
);
}
function formatMermaidSvgNotRenderedMessage(language) {
return modelWeaveText("SVG: not rendered", "SVG: \u672A\u63CF\u753B", language);
}
// src/renderers/graph-view-shared.ts
var DEFAULT_FIT_PADDING_PX = 16;
function resetGraphViewportState(state, initialZoom = 1) {
state.zoom = initialZoom;
state.panX = 0;
state.panY = 0;
state.viewMode = "fit";
state.hasAutoFitted = false;
state.hasUserInteracted = false;
}
function getConnectionPoints(source, target) {
const sourceCenterX = source.x + source.width / 2;
const sourceCenterY = source.y + source.height / 2;
const targetCenterX = target.x + target.width / 2;
const targetCenterY = target.y + target.height / 2;
const horizontal = Math.abs(targetCenterX - sourceCenterX) >= Math.abs(targetCenterY - sourceCenterY);
const startX = horizontal ? sourceCenterX < targetCenterX ? source.x + source.width : source.x : sourceCenterX;
const startY = horizontal ? sourceCenterY : sourceCenterY < targetCenterY ? source.y + source.height : source.y;
const endX = horizontal ? sourceCenterX < targetCenterX ? target.x : target.x + target.width : targetCenterX;
const endY = horizontal ? targetCenterY : sourceCenterY < targetCenterY ? target.y : target.y + target.height;
return {
startX,
startY,
endX,
endY,
midX: (startX + endX) / 2,
midY: (startY + endY) / 2
};
}
function computeSceneBounds(nodes, labels, padding = 24) {
const allRects = [...nodes, ...labels];
if (allRects.length === 0) {
return {
minX: 0,
minY: 0,
maxX: padding * 2,
maxY: padding * 2,
width: padding * 2,
height: padding * 2
};
}
const minX = Math.min(...allRects.map((rect) => rect.x)) - padding;
const minY = Math.min(...allRects.map((rect) => rect.y)) - padding;
const maxX = Math.max(...allRects.map((rect) => rect.x + rect.width)) + padding;
const maxY = Math.max(...allRects.map((rect) => rect.y + rect.height)) + padding;
return {
minX,
minY,
maxX,
maxY,
width: maxX - minX,
height: maxY - minY
};
}
function estimateBadgeBounds(centerX, centerY, value, options) {
const charWidth = options?.charWidth ?? 8;
const minWidth = options?.minWidth ?? 52;
const height = options?.height ?? 20;
const width = Math.max(minWidth, value.length * charWidth + 12);
return {
x: centerX - width / 2,
y: centerY - height / 2,
width,
height
};
}
function estimateEdgeLabelBounds(edges, layoutById, getLabel, labelOffsetY = -8) {
const bounds = [];
for (const edge of edges) {
const source = layoutById[edge.source];
const target = layoutById[edge.target];
if (!source || !target) {
continue;
}
const label = getLabel(edge);
if (!label) {
continue;
}
const points = getConnectionPoints(source, target);
bounds.push(estimateBadgeBounds(points.midX, points.midY + labelOffsetY, label));
}
return bounds;
}
function attachGraphViewportInteractions(canvas, surface, toolbar, scene, options) {
const minZoom = options?.minZoom ?? 0.45;
const maxZoom = options?.maxZoom ?? 2.4;
const initialZoom = options?.initialZoom ?? 1;
const minFitScale = options?.minFitScale ?? 0;
const fitPaddingPx = Math.max(0, options?.fitPaddingPx ?? DEFAULT_FIT_PADDING_PX);
const nodeSelector = options?.nodeSelector;
const fitHorizontalAlign = options?.fitHorizontalAlign ?? "center";
const fitVerticalAlign = options?.fitVerticalAlign ?? "center";
const fitContentBounds = options?.fitContentBounds;
const state = options?.viewportState ?? {
zoom: initialZoom,
panX: 0,
panY: 0,
viewMode: "fit",
hasAutoFitted: false,
hasUserInteracted: false
};
let isPanning = false;
let pointerId = null;
let startClientX = 0;
let startClientY = 0;
let startPanX = 0;
let startPanY = 0;
const applyTransform = () => {
surface.setCssProps({
"--mw-preview-transform": `translate(${state.panX}px, ${state.panY}px) scale(${state.zoom})`
});
toolbar.zoomLabel.textContent = `${Math.round(state.zoom * 100)}%`;
};
const notifyViewportStateChange = () => {
options?.onViewportStateChange?.(state);
};
const fitToView = () => {
const viewportWidth = canvas.clientWidth;
const viewportHeight = canvas.clientHeight;
if (viewportWidth <= 0 || viewportHeight <= 0) {
return false;
}
const boundsSource = scene.source ?? "scene";
const boundsX = scene.minX ?? 0;
const boundsY = scene.minY ?? 0;
const boundsWidth = scene.width;
const boundsHeight = scene.height;
const effectiveViewportWidth = Math.max(1, viewportWidth - fitPaddingPx * 2);
const effectiveViewportHeight = Math.max(1, viewportHeight - fitPaddingPx * 2);
if (!isValidFitBounds(boundsWidth, boundsHeight)) {
state.zoom = initialZoom;
state.panX = 0;
state.panY = 0;
applyTransform();
notifyViewportStateChange();
options?.onFitMetrics?.({
viewportWidth,
viewportHeight,
boundsX,
boundsY,
boundsWidth,
boundsHeight,
fitPaddingPx,
boundsSource,
computedScale: 0,
appliedScale: state.zoom,
panX: state.panX,
panY: state.panY,
warning: "fit skipped: invalid bounds"
});
return false;
}
const scaleX = effectiveViewportWidth / scene.width;
const scaleY = effectiveViewportHeight / scene.height;
const computedScale = Math.min(scaleX, scaleY);
if (computedScale < minFitScale) {
state.zoom = initialZoom;
state.panX = 0;
state.panY = 0;
applyTransform();
notifyViewportStateChange();
options?.onFitMetrics?.({
viewportWidth,
viewportHeight,
boundsX,
boundsY,
boundsWidth,
boundsHeight,
fitPaddingPx,
boundsSource,
computedScale,
appliedScale: state.zoom,
panX: state.panX,
panY: state.panY,
warning: "fit skipped: computed scale is below the safe threshold"
});
return false;
}
const nextZoom = clamp(computedScale, minZoom, maxZoom);
state.zoom = nextZoom;
state.panX = fitHorizontalAlign === "left" ? resolveLeftAlignedFitPan(viewportWidth, nextZoom, scene) : Math.max(0, (viewportWidth - scene.width * nextZoom) / 2);
state.panY = fitVerticalAlign === "top" ? resolveTopAlignedFitPan(viewportHeight, nextZoom, scene, fitContentBounds) : Math.max(0, (viewportHeight - scene.height * nextZoom) / 2);
state.viewMode = "fit";
applyTransform();
notifyViewportStateChange();
options?.onFitMetrics?.({
viewportWidth,
viewportHeight,
boundsX,
boundsY,
boundsWidth,
boundsHeight,
fitPaddingPx,
boundsSource,
computedScale,
appliedScale: nextZoom,
panX: state.panX,
panY: state.panY
});
return true;
};
const autoFitToView = () => {
if (state.hasUserInteracted) {
return;
}
const didFit = fitToView();
if (didFit) {
state.hasAutoFitted = true;
}
};
const zoomAtPoint = (nextZoom, clientX, clientY) => {
const clampedZoom = clamp(nextZoom, minZoom, maxZoom);
const rect = canvas.getBoundingClientRect();
const localX = clientX - rect.left;
const localY = clientY - rect.top;
const worldX = (localX - state.panX) / state.zoom;
const worldY = (localY - state.panY) / state.zoom;
state.zoom = clampedZoom;
state.panX = localX - worldX * clampedZoom;
state.panY = localY - worldY * clampedZoom;
applyTransform();
notifyViewportStateChange();
};
canvas.addEventListener(
"wheel",
(event) => {
if (!event.ctrlKey && !event.metaKey) {
return;
}
event.preventDefault();
state.hasUserInteracted = true;
state.hasAutoFitted = true;
state.viewMode = "manual";
const delta = event.deltaY < 0 ? 1.12 : 1 / 1.12;
zoomAtPoint(state.zoom * delta, event.clientX, event.clientY);
},
{ passive: false }
);
canvas.addEventListener("pointerdown", (event) => {
const target = event.target;
if (nodeSelector && target instanceof Element && target.closest(nodeSelector)) {
return;
}
state.hasUserInteracted = true;
state.hasAutoFitted = true;
state.viewMode = "manual";
isPanning = true;
pointerId = event.pointerId;
startClientX = event.clientX;
startClientY = event.clientY;
startPanX = state.panX;
startPanY = state.panY;
canvas.toggleClass("model-weave-is-grabbing", true);
canvas.setPointerCapture(event.pointerId);
});
canvas.addEventListener("pointermove", (event) => {
if (!isPanning || pointerId !== event.pointerId) {
return;
}
const dx = event.clientX - startClientX;
const dy = event.clientY - startClientY;
state.panX = startPanX + dx;
state.panY = startPanY + dy;
applyTransform();
notifyViewportStateChange();
});
const stopPanning = (event) => {
if (!isPanning || pointerId !== event.pointerId) {
return;
}
isPanning = false;
pointerId = null;
canvas.toggleClass("model-weave-is-grabbing", false);
if (canvas.hasPointerCapture(event.pointerId)) {
canvas.releasePointerCapture(event.pointerId);
}
};
canvas.addEventListener("pointerup", stopPanning);
canvas.addEventListener("pointercancel", stopPanning);
canvas.addEventListener("pointerleave", (event) => {
if (isPanning && pointerId === event.pointerId) {
stopPanning(event);
}
});
toolbar.zoomOutButton.addEventListener("click", () => {
state.hasUserInteracted = true;
state.hasAutoFitted = true;
state.viewMode = "manual";
state.zoom = clamp(state.zoom / 1.12, minZoom, maxZoom);
applyTransform();
notifyViewportStateChange();
});
toolbar.zoomInButton.addEventListener("click", () => {
state.hasUserInteracted = true;
state.hasAutoFitted = true;
state.viewMode = "manual";
state.zoom = clamp(state.zoom * 1.12, minZoom, maxZoom);
applyTransform();
notifyViewportStateChange();
});
toolbar.fitButton.addEventListener("click", () => {
state.hasUserInteracted = false;
if (fitToView()) {
state.hasAutoFitted = true;
}
});
toolbar.resetButton.addEventListener("click", () => {
state.hasUserInteracted = true;
state.hasAutoFitted = true;
state.viewMode = "manual";
state.zoom = initialZoom;
state.panX = 0;
state.panY = 0;
applyTransform();
notifyViewportStateChange();
});
const targetWindow = canvas.ownerDocument.defaultView ?? window;
targetWindow.requestAnimationFrame(() => {
if (!state.hasAutoFitted) {
autoFitToView();
} else {
applyTransform();
notifyViewportStateChange();
}
});
const resizeObserver = new ResizeObserver(() => {
if (!state.hasAutoFitted || state.viewMode === "fit") {
autoFitToView();
return;
}
applyTransform();
notifyViewportStateChange();
});
resizeObserver.observe(canvas);
}
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function isValidFitBounds(width, height) {
return Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0 && width < 1e5 && height < 1e5;
}
function resolveTopAlignedFitPan(viewportHeight, zoom, scene, fitContentBounds) {
const contentTop = fitContentBounds?.top ?? 0;
const contentBottom = fitContentBounds?.bottom ?? scene.height;
const contentHeight = Math.max(0, contentBottom - contentTop) * zoom;
const spareHeight = viewportHeight - contentHeight;
const topPadding = resolveAdaptiveEdgePadding(spareHeight);
return topPadding - contentTop * zoom;
}
function resolveLeftAlignedFitPan(viewportWidth, zoom, scene) {
const contentWidth = scene.width * zoom;
const spareWidth = viewportWidth - contentWidth;
const leftPadding = resolveAdaptiveEdgePadding(spareWidth);
return leftPadding;
}
function resolveAdaptiveEdgePadding(spareSpace) {
if (spareSpace >= 56) {
return 20;
}
if (spareSpace >= 16) {
return 8;
}
return 4;
}
// src/renderers/zoom-toolbar.ts
function createZoomToolbar(helpText, options = {}) {
const toolbar = activeDocument.createElement("div");
toolbar.className = "mdspec-zoom-toolbar model-weave-zoom-toolbar";
const leftGroup = activeDocument.createElement("div");
leftGroup.addClass("model-weave-zoom-toolbar-left");
const help = activeDocument.createElement("div");
help.addClass("model-weave-zoom-toolbar-help");
help.textContent = helpText;
leftGroup.appendChild(help);
const rightGroup = activeDocument.createElement("div");
rightGroup.addClass("model-weave-zoom-toolbar-right");
const controls = activeDocument.createElement("div");
controls.addClass("model-weave-zoom-toolbar-controls");
const zoomOutButton = createToolbarButton("\u2212");
const fitButton = createToolbarButton("Fit");
fitButton.addClass("model-weave-zoom-toolbar-fit");
const zoomLabel = activeDocument.createElement("span");
zoomLabel.addClass("model-weave-zoom-toolbar-label");
zoomLabel.textContent = "100%";
const zoomInButton = createToolbarButton("+");
const resetButton = createToolbarButton("100%");
const exportPngButton = options.onExportPng ? createToolbarButton("PNG") : null;
const exportAndOpenPngButton = options.onExportAndOpenPng ? createToolbarButton("PNG\u2197") : null;
if (exportPngButton) {
exportPngButton.addClass("model-weave-zoom-toolbar-export-png");
exportPngButton.setAttribute("aria-label", options.exportPngLabel ?? "Export as PNG");
exportPngButton.title = options.exportPngTitle ?? options.exportPngLabel ?? "Export as PNG";
exportPngButton.addEventListener("click", (event) => {
event.preventDefault();
void options.onExportPng?.();
});
}
if (exportAndOpenPngButton) {
exportAndOpenPngButton.addClass("model-weave-zoom-toolbar-export-open-png");
exportAndOpenPngButton.setAttribute(
"aria-label",
options.exportAndOpenPngLabel ?? "Export PNG and open"
);
exportAndOpenPngButton.title = options.exportAndOpenPngTitle ?? options.exportAndOpenPngLabel ?? "Export PNG and open";
exportAndOpenPngButton.addEventListener("click", (event) => {
event.preventDefault();
void options.onExportAndOpenPng?.();
});
}
controls.append(
zoomOutButton,
fitButton,
zoomLabel,
zoomInButton,
resetButton,
...exportPngButton ? [exportPngButton] : [],
...exportAndOpenPngButton ? [exportAndOpenPngButton] : []
);
rightGroup.appendChild(controls);
toolbar.append(leftGroup, rightGroup);
return {
root: toolbar,
zoomOutButton,
fitButton,
zoomLabel,
zoomInButton,
resetButton,
exportPngButton,
exportAndOpenPngButton,
leftGroup,
rightGroup
};
}
function createToolbarButton(label) {
const button = activeDocument.createElement("button");
button.type = "button";
button.textContent = label;
button.addClass("model-weave-zoom-toolbar-button");
return button;
}
// src/renderers/mermaid-shared.ts
var MODEL_WEAVE_MERMAID_RENDER_FLAG = "__modelWeaveRenderReady";
var MIN_ZOOM = 0.4;
var MAX_ZOOM = 2.25;
var INITIAL_ZOOM = 1;
function createMermaidShell(options) {
const root = activeDocument.createElement("section");
root.className = `${options.className} model-weave-mermaid-shell`;
if (options.title) {
const title = activeDocument.createElement("h2");
title.textContent = options.title;
title.title = options.title;
title.addClass("model-weave-mermaid-title");
root.appendChild(title);
}
const canvas = activeDocument.createElement("div");
canvas.addClass("model-weave-graph-canvas");
if (!options.forExport) {
canvas.addClass("model-weave-graph-canvas-interactive");
}
const toolbar = options.forExport ? null : createZoomToolbar("Ctrl/Meta + wheel: zoom / Drag background: pan", {
onExportPng: options.onExportPng,
onExportAndOpenPng: options.onExportAndOpenPng,
exportPngLabel: options.exportPngLabel,
exportPngTitle: options.exportPngTitle,
exportAndOpenPngLabel: options.exportAndOpenPngLabel,
exportAndOpenPngTitle: options.exportAndOpenPngTitle
});
if (toolbar) {
root.appendChild(toolbar.root);
}
const viewport = activeDocument.createElement("div");
viewport.addClass("model-weave-graph-viewport");
const surface = activeDocument.createElement("div");
surface.addClass("model-weave-graph-surface");
surface.dataset.modelWeaveExportSurface = "true";
viewport.appendChild(surface);
canvas.appendChild(viewport);
root.appendChild(canvas);
return { root, canvas, surface, toolbar };
}
async function renderMermaidSourceIntoShell(shell3, options) {
if (options.showSourcePanel !== false) {
appendMermaidSourcePanel(
options.sourcePanelContainer ?? shell3.root,
options.source,
options.sourcePanelPlacement,
{
title: options.sourcePanelTitle,
copyLabel: options.sourcePanelCopyLabel
}
);
}
const debug = options.showRenderDebug ? appendMermaidRenderDebugPanel(
options.renderDebugContainer ?? shell3.root,
options.renderDebugPlacement
) : null;
updateMermaidRenderDebug(debug, { status: "generated" });
try {
const mermaid = await loadMermaidAdapter();
const renderId = `${options.renderIdPrefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
const rendered = await mermaid.render(
renderId,
withModelWeaveMermaidTheme(options.source)
);
const { canvas, surface, toolbar } = shell3;
surface.empty();
const svg = appendRenderedSvg(surface, rendered.svg);
surface.dataset.modelWeaveRenderer = "mermaid";
if (typeof rendered.bindFunctions === "function") {
rendered.bindFunctions(surface);
}
const sceneSize = readMermaidSceneSize(svg);
if (!sceneSize) {
throw new Error("Mermaid SVG has no measurable bounds.");
}
surface.dataset.modelWeaveSceneWidth = `${sceneSize.width}`;
surface.dataset.modelWeaveSceneHeight = `${sceneSize.height}`;
surface.setCssProps({
"--mw-scene-width": `${sceneSize.width}px`,
"--mw-scene-height": `${sceneSize.height}px`
});
svg.setAttribute("width", `${sceneSize.width}`);
svg.setAttribute("height", `${sceneSize.height}`);
svg.classList.add("model-weave-mermaid-svg");
updateMermaidRenderDebug(debug, {
status: "rendered",
svg: readMermaidSvgInfo(surface)
});
if (options.staticRender) {
canvas.addClass("model-weave-graph-canvas-static");
surface.addClass("model-weave-graph-surface-static");
svg.classList.add("model-weave-mermaid-svg-static");
return;
}
if (toolbar) {
attachGraphViewportInteractions(canvas, surface, toolbar, sceneSize, {
minZoom: options.minZoom ?? MIN_ZOOM,
maxZoom: options.maxZoom ?? MAX_ZOOM,
initialZoom: options.initialZoom ?? INITIAL_ZOOM,
minFitScale: options.minFitScale,
nodeSelector: options.nodeSelector ?? ".node, g.node, foreignObject",
fitHorizontalAlign: options.fitHorizontalAlign,
fitVerticalAlign: options.fitVerticalAlign,
viewportState: options.viewportState,
onViewportStateChange: options.onViewportStateChange,
onFitMetrics: (metrics) => {
updateMermaidRenderDebug(debug, { fit: metrics });
options.onFitMetrics?.(metrics);
}
});
}
} catch (error) {
updateMermaidRenderDebug(debug, {
status: "failed",
error: error instanceof Error ? error.message : String(error),
svg: readMermaidSvgInfo(shell3.surface)
});
throw error;
}
}
function appendMermaidSourcePanel(container, source, placement = "append", labels) {
const fencedSource = `\`\`\`mermaid
${source}
\`\`\``;
const root = container.ownerDocument.createElement("details");
root.addClass("model-weave-preview-section");
root.addClass("model-weave-mermaid-source-panel");
const summary = container.ownerDocument.createElement("summary");
summary.textContent = labels?.title ?? modelWeaveText("Mermaid source", "Mermaid \u30BD\u30FC\u30B9");
summary.addClass("model-weave-preview-section-title");
root.appendChild(summary);
const actions = container.ownerDocument.createElement("div");
actions.addClass("model-weave-mermaid-source-actions");
const copyButton = container.ownerDocument.createElement("button");
copyButton.type = "button";
copyButton.textContent = labels?.copyLabel ?? modelWeaveText("Copy Mermaid", "Mermaid \u3092\u30B3\u30D4\u30FC");
copyButton.addClass("model-weave-secondary-button");
copyButton.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
void navigator.clipboard?.writeText(fencedSource);
});
actions.appendChild(copyButton);
root.appendChild(actions);
const pre = container.ownerDocument.createElement("pre");
pre.addClass("model-weave-mermaid-source-code");
const code = container.ownerDocument.createElement("code");
code.textContent = fencedSource;
pre.appendChild(code);
root.appendChild(pre);
placePanel(container, root, placement);
}
function appendMermaidRenderDebugPanel(container, placement = "append") {
const root = container.ownerDocument.createElement("details");
root.addClass("model-weave-preview-section");
root.addClass("model-weave-mermaid-render-debug");
const summary = container.ownerDocument.createElement("summary");
summary.textContent = modelWeaveText(
"Mermaid render debug",
"Mermaid render debug"
);
summary.addClass("model-weave-preview-section-title");
root.appendChild(summary);
const status = root.createEl("p", {
text: formatMermaidRenderStatusMessage("generated"),
cls: "model-weave-summary-muted"
});
const error = root.createEl("p", {
text: formatMermaidRenderErrorMessage("-"),
cls: "model-weave-summary-muted"
});
const svgInfo = root.createEl("p", {
text: formatMermaidSvgNotRenderedMessage(),
cls: "model-weave-summary-muted"
});
const fitInfo = root.createEl("p", {
text: "Fit: not measured",
cls: "model-weave-summary-muted"
});
placePanel(container, root, placement);
return { root, status, error, svgInfo, fitInfo };
}
function placePanel(container, panel, placement) {
if (placement === "prepend") {
container.prepend(panel);
return;
}
container.appendChild(panel);
}
function updateMermaidRenderDebug(debug, update) {
if (!debug) {
return;
}
if (update.status) {
debug.status.textContent = formatMermaidRenderStatusMessage(update.status);
}
if (update.error) {
debug.error.textContent = formatMermaidRenderErrorMessage(update.error);
}
if (update.svg) {
debug.svgInfo.textContent = [
`SVG exists: ${update.svg.exists ? "yes" : "no"}`,
`width: ${update.svg.width || "-"}`,
`height: ${update.svg.height || "-"}`,
`viewBox: ${update.svg.viewBox || "-"}`,
`child elements: ${update.svg.childElementCount}`
].join(" / ");
}
if (update.fit) {
debug.fitInfo.textContent = [
modelWeaveText(
`Fit bounds source: ${update.fit.boundsSource}`,
`fit bounds source: ${update.fit.boundsSource}`
),
`viewport: ${formatFitNumber(update.fit.viewportWidth)}x${formatFitNumber(update.fit.viewportHeight)}`,
`bounds: ${formatFitNumber(update.fit.boundsX)},${formatFitNumber(update.fit.boundsY)} ${formatFitNumber(update.fit.boundsWidth)}x${formatFitNumber(update.fit.boundsHeight)}`,
`computed scale: ${formatFitPercent(update.fit.computedScale)}`,
`applied scale: ${formatFitPercent(update.fit.appliedScale)}`,
`pan: ${formatFitNumber(update.fit.panX)},${formatFitNumber(update.fit.panY)}`,
update.fit.warning ? modelWeaveText(`warning: ${update.fit.warning}`, `\u8B66\u544A: ${update.fit.warning}`) : null
].filter((part) => Boolean(part)).join(" / ");
}
}
function readMermaidSvgInfo(surface) {
const svg = surface.querySelector("svg");
return {
exists: Boolean(svg),
width: svg?.getAttribute("width") ?? "",
height: svg?.getAttribute("height") ?? "",
viewBox: svg?.getAttribute("viewBox") ?? "",
childElementCount: svg?.childElementCount ?? 0
};
}
function formatFitNumber(value) {
if (!Number.isFinite(value)) {
return "-";
}
return Math.round(value * 100) / 100 + "";
}
function formatFitPercent(value) {
if (!Number.isFinite(value)) {
return "-";
}
return `${Math.round(value * 1e3) / 10}%`;
}
function appendRenderedSvg(surface, svgMarkup) {
const parsedSvg = parseMermaidSvgMarkup(svgMarkup);
if (!parsedSvg) {
throw new Error("Mermaid SVG was not generated.");
}
scrubSvgElementTree(parsedSvg);
const importedSvg = surface.ownerDocument.importNode(parsedSvg, true);
if (!importedSvg.instanceOf(SVGSVGElement)) {
throw new Error("Mermaid SVG import did not produce an SVG element.");
}
surface.appendChild(importedSvg);
return importedSvg;
}
function parseMermaidSvgMarkup(svgMarkup) {
const parser = new DOMParser();
const svgDocument = parser.parseFromString(svgMarkup, "image/svg+xml");
const svgParseError = svgDocument.querySelector("parsererror");
if (!svgParseError) {
const parsedSvg = svgDocument.documentElement;
if (parsedSvg && parsedSvg.tagName.toLowerCase() === "svg") {
return parsedSvg.instanceOf(SVGSVGElement) ? parsedSvg : null;
}
}
const htmlDocument = parser.parseFromString(svgMarkup, "text/html");
const htmlSvg = htmlDocument.body.querySelector("svg");
if (!htmlSvg) {
return null;
}
return htmlSvg.instanceOf(SVGSVGElement) ? htmlSvg : null;
}
function scrubSvgElementTree(root) {
const elements = [root, ...Array.from(root.querySelectorAll("*"))];
for (const element of elements) {
if (element.tagName.toLowerCase() === "script") {
element.remove();
continue;
}
for (const attribute of Array.from(element.attributes)) {
const attributeName = attribute.name.toLowerCase();
const attributeValue = attribute.value.trim().toLowerCase();
if (attributeName.startsWith("on")) {
element.removeAttribute(attribute.name);
continue;
}
if ((attributeName === "href" || attributeName === "xlink:href") && attributeValue.startsWith("javascript:")) {
element.removeAttribute(attribute.name);
}
}
}
}
function setMermaidRenderReadyPromise(element, ready) {
element[MODEL_WEAVE_MERMAID_RENDER_FLAG] = ready;
}
function getMermaidRenderReadyPromise(element) {
return element[MODEL_WEAVE_MERMAID_RENDER_FLAG] ?? null;
}
function createMermaidFallbackNotice(message) {
const notice = activeDocument.createElement("div");
notice.addClass("model-weave-mermaid-fallback");
notice.textContent = message;
return notice;
}
function getModelWeaveMermaidPalette() {
const isDark = isModelWeaveDarkTheme();
if (isDark) {
return {
background: "#1f2329",
nodeFill: "#273241",
nodeBorder: "#6f8fb8",
nodeText: "#e6edf3",
line: "#9aa7b8",
labelBackground: "#2a3038",
subgraphFill: "#242a33",
subgraphBorder: "#5f6f82",
classFill: "#273241",
classBorder: "#6f8fb8",
erFill: "#243629",
erBorder: "#70a57d",
dfdExternalFill: "#3a3325",
dfdExternalBorder: "#b69a58",
dfdProcessFill: "#253349",
dfdProcessBorder: "#6f8fb8",
dfdDatastoreFill: "#253728",
dfdDatastoreBorder: "#78a984",
dfdOtherFill: "#2b3038",
dfdOtherBorder: "#7a8797"
};
}
return {
background: "#ffffff",
nodeFill: "#f4f7fb",
nodeBorder: "#7a8da8",
nodeText: "#1f2937",
line: "#64748b",
labelBackground: "#f8fafc",
subgraphFill: "#f5f7fa",
subgraphBorder: "#c5ceda",
classFill: "#eef4ff",
classBorder: "#4a6fa3",
erFill: "#eef8ef",
erBorder: "#467454",
dfdExternalFill: "#f8f1df",
dfdExternalBorder: "#8b6a17",
dfdProcessFill: "#e9f2ff",
dfdProcessBorder: "#2f5b9a",
dfdDatastoreFill: "#eef7ee",
dfdDatastoreBorder: "#3b6b47",
dfdOtherFill: "#f5f7fb",
dfdOtherBorder: "#5f6b7a"
};
}
function buildModelWeaveMermaidClassDef(className, fill, stroke, options) {
const palette = getModelWeaveMermaidPalette();
const text = options?.text ?? palette.nodeText;
const strokeWidth = options?.strokeWidth ?? 1.4;
const extra = options?.extra ? `,${options.extra}` : "";
return `classDef ${className} fill:${fill},stroke:${stroke},color:${text},stroke-width:${strokeWidth}px${extra}`;
}
function withModelWeaveMermaidTheme(source) {
if (/^\s*%%\{init:/u.test(source)) {
return source;
}
return `${buildModelWeaveMermaidInitDirective()}
${source}`;
}
function buildModelWeaveMermaidInitDirective() {
const palette = getModelWeaveMermaidPalette();
return `%%{init: ${JSON.stringify({
theme: "base",
themeVariables: {
background: palette.background,
mainBkg: palette.nodeFill,
secondBkg: palette.subgraphFill,
primaryColor: palette.nodeFill,
primaryBorderColor: palette.nodeBorder,
primaryTextColor: palette.nodeText,
secondaryColor: palette.subgraphFill,
secondaryBorderColor: palette.subgraphBorder,
secondaryTextColor: palette.nodeText,
tertiaryColor: palette.labelBackground,
tertiaryBorderColor: palette.subgraphBorder,
tertiaryTextColor: palette.nodeText,
lineColor: palette.line,
textColor: palette.nodeText,
edgeLabelBackground: palette.labelBackground,
clusterBkg: palette.subgraphFill,
clusterBorder: palette.subgraphBorder,
titleColor: palette.nodeText,
nodeBorder: palette.nodeBorder
}
})}}%%`;
}
function isModelWeaveDarkTheme() {
return activeDocument.body.classList.contains("theme-dark");
}
function readMermaidSceneSize(svg) {
const viewBox = svg.viewBox?.baseVal;
if (viewBox && Number.isFinite(viewBox.width) && Number.isFinite(viewBox.height)) {
return {
minX: viewBox.x,
minY: viewBox.y,
maxX: viewBox.x + Math.max(1, viewBox.width),
maxY: viewBox.y + Math.max(1, viewBox.height),
width: Math.max(1, viewBox.width),
height: Math.max(1, viewBox.height),
source: "viewBox"
};
}
const bbox = safeReadSvgBBox(svg);
if (bbox) {
return {
minX: bbox.x,
minY: bbox.y,
maxX: bbox.x + bbox.width,
maxY: bbox.y + bbox.height,
width: bbox.width,
height: bbox.height,
source: "getBBox"
};
}
const width = parseFloat(svg.getAttribute("width") ?? "");
const height = parseFloat(svg.getAttribute("height") ?? "");
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
return null;
}
return {
minX: 0,
minY: 0,
maxX: width,
maxY: height,
width,
height,
source: "fallback"
};
}
function safeReadSvgBBox(svg) {
try {
const bbox = svg.getBBox();
if (Number.isFinite(bbox.x) && Number.isFinite(bbox.y) && Number.isFinite(bbox.width) && Number.isFinite(bbox.height) && bbox.width > 0 && bbox.height > 0) {
return {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height
};
}
} catch {
return null;
}
return null;
}
// src/export/png-export.ts
var EXPORT_FOLDER = "exports";
var EXPORT_PADDING = 32;
var EXPORT_SCALE = 2;
var OFFSCREEN_ROOT_ID = "model-weave-export-root";
var DiagramExportError = class extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = "DiagramExportError";
}
};
async function exportDiagramRenderableAsPng(app, renderable) {
const rendered = await Promise.resolve(renderable.render());
const mounted = mountOffscreenExportRoot(rendered);
try {
const mermaidReady = getMermaidRenderReadyPromise(rendered);
if (mermaidReady) {
await mermaidReady;
}
await waitForAnimationFrame();
const snapshot = buildDomDiagramExportSnapshot(
mounted.mount,
renderable.filePath,
renderable.renderer
);
if (!snapshot) {
throw new DiagramExportError(
"The current diagram has no measurable export bounds.",
"bounds-invalid"
);
}
return exportDiagramSnapshotAsPng(app, snapshot);
} finally {
mounted.dispose();
}
}
function buildDomDiagramExportSnapshot(container, filePath, renderer) {
const surface = container.querySelector(
'[data-model-weave-export-surface="true"]'
);
if (!surface) {
return null;
}
const surfaceComputedStyle = window.getComputedStyle(surface);
const sceneWidth = readSceneSize(
surface.dataset.modelWeaveSceneWidth,
surfaceComputedStyle.width
);
const sceneHeight = readSceneSize(
surface.dataset.modelWeaveSceneHeight,
surfaceComputedStyle.height
);
if (!sceneWidth || !sceneHeight) {
return null;
}
return {
filePath,
surface,
sceneWidth,
sceneHeight,
renderer: surface.dataset.modelWeaveRenderer,
filenameRenderer: renderer ?? surface.dataset.modelWeaveRenderer
};
}
async function exportDiagramSnapshotAsPng(app, snapshot) {
const arrayBuffer = await renderSnapshotToPng(snapshot);
try {
await ensureFolder(app, EXPORT_FOLDER);
const exportPath = `${EXPORT_FOLDER}/${toExportFileName(
snapshot.filePath,
snapshot.filenameRenderer ?? snapshot.renderer
)}.png`;
const existing = app.vault.getAbstractFileByPath(exportPath);
if (existing instanceof import_obsidian4.TFile) {
await app.vault.modifyBinary(existing, arrayBuffer);
} else {
await app.vault.createBinary(exportPath, arrayBuffer);
}
return exportPath;
} catch {
throw new DiagramExportError("Failed to save PNG export.", "save-failed");
}
}
async function renderSnapshotToPng(snapshot) {
if (snapshot.renderer === "mermaid") {
return renderMermaidSnapshotToPng(snapshot);
}
const exportWidth = snapshot.sceneWidth + EXPORT_PADDING * 2;
const exportHeight = snapshot.sceneHeight + EXPORT_PADDING * 2;
if (!Number.isFinite(exportWidth) || !Number.isFinite(exportHeight) || exportWidth <= 0 || exportHeight <= 0) {
throw new DiagramExportError(
"The current diagram has no measurable export bounds.",
"bounds-invalid"
);
}
const wrapper = createDiv();
wrapper.addClass("model-weave-export-wrapper");
wrapper.setCssProps({
"--mw-export-width": `${exportWidth}px`,
"--mw-export-height": `${exportHeight}px`
});
const clone = snapshot.surface.cloneNode(true);
if (!clone.instanceOf(HTMLElement)) {
throw new DiagramExportError("Failed to clone the diagram surface.", "render-failed");
}
prepareSurfaceClone(clone, snapshot, exportWidth, exportHeight);
wrapper.appendChild(clone);
const svg = buildExportSvg(wrapper, exportWidth, exportHeight);
const serialized = new XMLSerializer().serializeToString(svg);
const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`;
try {
const image = await loadImage(svgUrl);
const canvas = createEl("canvas");
canvas.width = Math.ceil(exportWidth * EXPORT_SCALE);
canvas.height = Math.ceil(exportHeight * EXPORT_SCALE);
const context = canvas.getContext("2d");
if (!context) {
throw new DiagramExportError(
modelWeaveText(
"Canvas rendering context is not available.",
"Canvas rendering context \u304C\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002"
),
"render-failed"
);
}
context.fillStyle = "#ffffff";
context.fillRect(0, 0, canvas.width, canvas.height);
context.setTransform(EXPORT_SCALE, 0, 0, EXPORT_SCALE, 0, 0);
context.drawImage(image, 0, 0, exportWidth, exportHeight);
const pngBlob = await canvasToBlob(canvas);
const arrayBuffer = await pngBlob.arrayBuffer();
if (arrayBuffer.byteLength <= 0) {
throw new DiagramExportError("Failed to encode PNG image.", "encode-failed");
}
return arrayBuffer;
} catch (error) {
if (error instanceof DiagramExportError) {
throw error;
}
throw new DiagramExportError(
modelWeaveText("Failed to render diagram PNG.", "diagram PNG \u306E\u63CF\u753B\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002"),
"render-failed"
);
}
}
async function renderMermaidSnapshotToPng(snapshot) {
const svg = snapshot.surface.querySelector("svg");
if (!svg) {
throw new DiagramExportError(
modelWeaveText(
"Mermaid SVG export source was not found.",
"Mermaid SVG export source \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002"
),
"render-failed"
);
}
const contentBounds = measureMermaidContentBounds(svg);
if (!contentBounds) {
return renderSnapshotToPng({
...snapshot,
renderer: "custom"
});
}
const exportWidth = contentBounds.width + EXPORT_PADDING * 2;
const exportHeight = contentBounds.height + EXPORT_PADDING * 2;
const viewBoxX = contentBounds.x - EXPORT_PADDING;
const viewBoxY = contentBounds.y - EXPORT_PADDING;
const palette = getModelWeaveMermaidPalette();
const clone = svg.cloneNode(true);
if (!clone.instanceOf(SVGSVGElement)) {
throw new DiagramExportError("Failed to clone the Mermaid SVG.", "render-failed");
}
inlineSvgStyles(svg, clone);
clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
clone.setAttribute("width", `${exportWidth}`);
clone.setAttribute("height", `${exportHeight}`);
clone.setAttribute(
"viewBox",
`${viewBoxX} ${viewBoxY} ${exportWidth} ${exportHeight}`
);
clone.addClass("model-weave-export-mermaid-clone");
clone.setCssProps({
"--mw-export-width": `${exportWidth}px`,
"--mw-export-height": `${exportHeight}px`
});
const background = createSvg("rect");
background.setAttribute("x", String(viewBoxX));
background.setAttribute("y", String(viewBoxY));
background.setAttribute("width", String(exportWidth));
background.setAttribute("height", String(exportHeight));
background.setAttribute("fill", palette.background);
clone.insertBefore(background, clone.firstChild);
const serialized = new XMLSerializer().serializeToString(clone);
const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`;
try {
const image = await loadImage(svgUrl);
const canvas = createEl("canvas");
canvas.width = Math.ceil(exportWidth * EXPORT_SCALE);
canvas.height = Math.ceil(exportHeight * EXPORT_SCALE);
const context = canvas.getContext("2d");
if (!context) {
throw new DiagramExportError(
modelWeaveText(
"Canvas rendering context is not available.",
"Canvas rendering context \u304C\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002"
),
"render-failed"
);
}
context.fillStyle = palette.background;
context.fillRect(0, 0, canvas.width, canvas.height);
context.setTransform(EXPORT_SCALE, 0, 0, EXPORT_SCALE, 0, 0);
context.drawImage(image, 0, 0, exportWidth, exportHeight);
const pngBlob = await canvasToBlob(canvas);
const arrayBuffer = await pngBlob.arrayBuffer();
if (arrayBuffer.byteLength <= 0) {
throw new DiagramExportError("Failed to encode PNG image.", "encode-failed");
}
return arrayBuffer;
} catch (error) {
if (error instanceof DiagramExportError) {
throw error;
}
throw new DiagramExportError(
modelWeaveText(
"Failed to render Mermaid diagram PNG.",
"Mermaid diagram PNG \u306E\u63CF\u753B\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002"
),
"render-failed"
);
}
}
function mountOffscreenExportRoot(root) {
const exportDocument = root.ownerDocument ?? activeDocument;
const host = exportDocument.createElement("div");
host.id = OFFSCREEN_ROOT_ID;
host.addClass("model-weave-export-offscreen-root");
host.appendChild(root);
exportDocument.body.appendChild(host);
return {
mount: host,
dispose: () => host.remove()
};
}
function prepareSurfaceClone(clone, snapshot, exportWidth, exportHeight) {
inlineComputedStyles(snapshot.surface, clone);
clone.addClass("model-weave-export-surface-clone");
clone.setCssProps({
"--mw-export-left": `${EXPORT_PADDING}px`,
"--mw-export-top": `${EXPORT_PADDING}px`,
"--mw-export-width": `${snapshot.sceneWidth}px`,
"--mw-export-height": `${snapshot.sceneHeight}px`,
"--mw-export-min-width": `${snapshot.sceneWidth}px`,
"--mw-export-min-height": `${snapshot.sceneHeight}px`,
"--mw-export-transform": "none"
});
for (const toolbar of Array.from(clone.querySelectorAll(".mdspec-zoom-toolbar"))) {
toolbar.remove();
}
for (const details of Array.from(
clone.querySelectorAll(
".mdspec-related-list, .mdspec-connections, .mdspec-relations-table"
)
)) {
details.remove();
}
const root = clone.closest("section");
if (root?.instanceOf(HTMLElement)) {
root.addClass("model-weave-export-root-background");
}
const svgs = Array.from(clone.querySelectorAll("svg"));
for (const svg of svgs) {
svg.setAttribute("width", `${exportWidth}`);
svg.setAttribute("height", `${exportHeight}`);
}
}
function buildExportSvg(wrapper, exportWidth, exportHeight) {
const svg = createSvg("svg");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
svg.setAttribute("width", `${exportWidth}`);
svg.setAttribute("height", `${exportHeight}`);
svg.setAttribute("viewBox", `0 0 ${exportWidth} ${exportHeight}`);
const foreignObject = createSvg("foreignObject");
foreignObject.setAttribute("x", "0");
foreignObject.setAttribute("y", "0");
foreignObject.setAttribute("width", `${exportWidth}`);
foreignObject.setAttribute("height", `${exportHeight}`);
foreignObject.appendChild(wrapper);
svg.appendChild(foreignObject);
return svg;
}
function readSceneSize(datasetValue, styleValue) {
const preferred = Number.parseFloat(datasetValue ?? "");
if (Number.isFinite(preferred) && preferred > 0) {
return preferred;
}
const fallback = Number.parseFloat(styleValue);
return Number.isFinite(fallback) && fallback > 0 ? fallback : null;
}
function measureMermaidContentBounds(svg) {
const candidates = [
svg.querySelector("g.output"),
svg.querySelector("g.root"),
svg.querySelector("g.flowchart"),
svg.querySelector("svg > g"),
svg.querySelector("g")
].filter((value) => Boolean(value));
for (const candidate of candidates) {
const bbox = safeGetBBox(candidate);
if (bbox) {
return bbox;
}
}
const svgBox = safeGetBBox(svg);
if (svgBox) {
return svgBox;
}
const rect = svg.getBoundingClientRect();
if (Number.isFinite(rect.width) && Number.isFinite(rect.height) && rect.width > 0 && rect.height > 0) {
return {
x: 0,
y: 0,
width: rect.width,
height: rect.height
};
}
return null;
}
function safeGetBBox(element) {
try {
const bbox = element.getBBox();
if (Number.isFinite(bbox.width) && Number.isFinite(bbox.height) && bbox.width > 0 && bbox.height > 0) {
return {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height
};
}
} catch {
return null;
}
return null;
}
async function ensureFolder(app, folderPath) {
const existing = app.vault.getAbstractFileByPath(folderPath);
if (existing) {
return;
}
await app.vault.createFolder(folderPath);
}
function toExportFileName(filePath, renderer) {
const normalized = filePath.replace(/\\/g, "/");
const basename = normalized.split("/").pop() ?? normalized;
const modelName = sanitizeExportFileNamePart(
basename.replace(/\.md$/i, "") || "diagram"
);
const rendererName = sanitizeExportFileNamePart(renderer || "default");
return `${modelName}__${rendererName}`;
}
function sanitizeExportFileNamePart(value) {
const sanitized = value.replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, " ").trim();
return sanitized || "default";
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = () => reject(new DiagramExportError(
modelWeaveText(
"Failed to render diagram image.",
"diagram image \u306E\u63CF\u753B\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002"
),
"render-failed"
));
image.src = url;
});
}
function canvasToBlob(canvas) {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
resolve(blob);
return;
}
reject(new DiagramExportError("Failed to encode PNG image.", "encode-failed"));
}, "image/png");
});
}
function inlineComputedStyles(source, target) {
const computed = window.getComputedStyle(source);
applyComputedStyle(target, computed);
const sourceChildren = Array.from(source.children);
const targetChildren = Array.from(target.children);
for (let index = 0; index < sourceChildren.length; index += 1) {
const sourceChild = sourceChildren[index];
const targetChild = targetChildren[index];
if (sourceChild.instanceOf(HTMLElement) && targetChild.instanceOf(HTMLElement)) {
inlineComputedStyles(sourceChild, targetChild);
continue;
}
if (sourceChild.instanceOf(SVGElement) && targetChild.instanceOf(SVGElement)) {
inlineSvgStyles(sourceChild, targetChild);
}
}
}
function inlineSvgStyles(source, target) {
const computed = window.getComputedStyle(source);
applyComputedStyle(target, computed);
const sourceChildren = Array.from(source.children);
const targetChildren = Array.from(target.children);
for (let index = 0; index < sourceChildren.length; index += 1) {
const sourceChild = sourceChildren[index];
const targetChild = targetChildren[index];
if (sourceChild.instanceOf(SVGElement) && targetChild.instanceOf(SVGElement)) {
inlineSvgStyles(sourceChild, targetChild);
} else if (sourceChild.instanceOf(HTMLElement) && targetChild.instanceOf(HTMLElement)) {
inlineComputedStyles(sourceChild, targetChild);
}
}
}
function applyComputedStyle(target, computed) {
const style = Reflect.get(target, "style");
if (!(style instanceof CSSStyleDeclaration)) {
return;
}
for (let index = 0; index < computed.length; index += 1) {
const property = computed.item(index);
const value = computed.getPropertyValue(property);
const priority = computed.getPropertyPriority(property);
if (value) {
style.setProperty(property, value, priority);
}
}
}
function waitForAnimationFrame() {
return new Promise((resolve) => {
window.requestAnimationFrame(() => resolve());
});
}
// src/core/app-process-business-flow-direction.ts
function normalizeAppProcessBusinessFlowDirection(value) {
if (typeof value !== "string") {
return void 0;
}
const normalized = value.trim().toUpperCase();
return normalized === "LR" || normalized === "TD" ? normalized : void 0;
}
function normalizeAppProcessBusinessFlowDirectionWithFallback(value) {
return normalizeAppProcessBusinessFlowDirection(value) ?? "LR";
}
function resolveAppProcessBusinessFlowDirection(input) {
return normalizeAppProcessBusinessFlowDirection(input.toolbarOverride) ?? normalizeAppProcessBusinessFlowDirection(input.frontmatterDirection) ?? normalizeAppProcessBusinessFlowDirection(input.settingsDefaultDirection) ?? "LR";
}
// src/settings/model-weave-settings.ts
var DOMAIN_VIEW_MODE_SETTING_OPTIONS = [
{ value: "mindmap", label: "Mindmap" },
{ value: "area", label: "Area" },
{ value: "tree", label: "Tree" }
];
var DEFAULT_MODEL_WEAVE_SETTINGS = {
defaultClassRenderMode: "custom",
defaultErRenderMode: "custom",
defaultDfdRenderMode: "mermaid",
defaultProcessRenderMode: "custom",
defaultBusinessFlowDirection: "LR",
defaultScreenRenderMode: "custom",
defaultFlowDiagramViewMode: "detail",
defaultDomainsViewMode: "mindmap",
defaultDomainDiagramViewMode: "mindmap",
defaultZoom: "fit",
fontSize: "normal",
nodeDensity: "normal",
localSourceRoot: "",
defaultColorSchemeRef: "",
enableRelationshipView: true,
showMermaidRenderDebug: false,
uiLanguage: "auto"
};
var VALID_DEFAULT_ZOOMS = /* @__PURE__ */ new Set(["fit", "100"]);
var VALID_FONT_SIZES = /* @__PURE__ */ new Set([
"small",
"normal",
"large"
]);
var VALID_NODE_DENSITIES = /* @__PURE__ */ new Set([
"compact",
"normal",
"relaxed"
]);
var VALID_RENDER_MODES2 = /* @__PURE__ */ new Set([
"custom",
"mermaid",
"mermaid-detail"
]);
var CLASS_RENDER_MODES = /* @__PURE__ */ new Set([
"custom",
"mermaid",
"mermaid-detail"
]);
var ER_RENDER_MODES = /* @__PURE__ */ new Set([
"custom",
"mermaid",
"mermaid-detail"
]);
var DFD_RENDER_MODES = /* @__PURE__ */ new Set(["mermaid"]);
var PROCESS_RENDER_MODES = /* @__PURE__ */ new Set(["custom"]);
var SCREEN_RENDER_MODES = /* @__PURE__ */ new Set(["custom"]);
var VALID_DOMAIN_VIEW_MODES = /* @__PURE__ */ new Set([
"mindmap",
"area",
"tree"
]);
var VALID_FLOW_DIAGRAM_VIEW_MODES = /* @__PURE__ */ new Set([
"detail",
"screen"
]);
var VALID_UI_LANGUAGES = /* @__PURE__ */ new Set(["auto", "en", "ja"]);
function normalizeModelWeaveSettings(value) {
const raw = isRecord(value) ? value : {};
const legacyDefaultRenderMode = normalizeEnumValue(
raw.defaultRenderMode,
VALID_RENDER_MODES2,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultClassRenderMode
);
return {
defaultClassRenderMode: normalizeEnumValue(
raw.defaultClassRenderMode ?? legacyDefaultRenderMode,
CLASS_RENDER_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultClassRenderMode
),
defaultErRenderMode: normalizeEnumValue(
raw.defaultErRenderMode ?? legacyDefaultRenderMode,
ER_RENDER_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultErRenderMode
),
defaultDfdRenderMode: normalizeEnumValue(
raw.defaultDfdRenderMode,
DFD_RENDER_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultDfdRenderMode
),
defaultProcessRenderMode: normalizeEnumValue(
raw.defaultProcessRenderMode,
PROCESS_RENDER_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultProcessRenderMode
),
defaultBusinessFlowDirection: normalizeAppProcessBusinessFlowDirectionWithFallback(
raw.defaultBusinessFlowDirection
),
defaultScreenRenderMode: normalizeEnumValue(
raw.defaultScreenRenderMode,
SCREEN_RENDER_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultScreenRenderMode
),
defaultFlowDiagramViewMode: normalizeEnumValue(
raw.defaultFlowDiagramViewMode,
VALID_FLOW_DIAGRAM_VIEW_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultFlowDiagramViewMode
),
defaultDomainsViewMode: normalizeEnumValue(
raw.defaultDomainsViewMode,
VALID_DOMAIN_VIEW_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultDomainsViewMode
),
defaultDomainDiagramViewMode: normalizeEnumValue(
raw.defaultDomainDiagramViewMode,
VALID_DOMAIN_VIEW_MODES,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultDomainDiagramViewMode
),
defaultZoom: normalizeEnumValue(
raw.defaultZoom,
VALID_DEFAULT_ZOOMS,
DEFAULT_MODEL_WEAVE_SETTINGS.defaultZoom
),
fontSize: normalizeEnumValue(
raw.fontSize,
VALID_FONT_SIZES,
DEFAULT_MODEL_WEAVE_SETTINGS.fontSize
),
nodeDensity: normalizeEnumValue(
raw.nodeDensity,
VALID_NODE_DENSITIES,
DEFAULT_MODEL_WEAVE_SETTINGS.nodeDensity
),
localSourceRoot: normalizeStringValue(raw.localSourceRoot ?? raw.sourceRoot),
defaultColorSchemeRef: normalizeStringValue(raw.defaultColorSchemeRef),
enableRelationshipView: normalizeBooleanValue(
raw.enableRelationshipView,
DEFAULT_MODEL_WEAVE_SETTINGS.enableRelationshipView
),
showMermaidRenderDebug: normalizeBooleanValue(
raw.showMermaidRenderDebug,
DEFAULT_MODEL_WEAVE_SETTINGS.showMermaidRenderDebug
),
uiLanguage: normalizeEnumValue(
raw.uiLanguage,
VALID_UI_LANGUAGES,
DEFAULT_MODEL_WEAVE_SETTINGS.uiLanguage
)
};
}
function normalizeStringValue(value) {
return typeof value === "string" ? value.trim() : "";
}
function normalizeBooleanValue(value, fallback) {
return typeof value === "boolean" ? value : fallback;
}
function normalizeEnumValue(value, allowed, fallback) {
if (typeof value !== "string") {
return fallback;
}
const normalized = value.trim().toLowerCase();
return allowed.has(normalized) ? normalized : fallback;
}
function isRecord(value) {
return typeof value === "object" && value !== null;
}
// src/templates/model-weave-templates.ts
var MODEL_WEAVE_TEMPLATES = {
class: `---
type: class
id: CLS-
name:
kind: class
package:
stereotype:
tags:
- Class
---
#
## Summary
## Attributes
| name | type | visibility | static | notes |
|---|---|---|---|---|
## Methods
| name | parameters | returns | visibility | static | notes |
|---|---|---|---|---|---|
## Relations
| id | to | kind | label | from_multiplicity | to_multiplicity | notes |
|---|---|---|---|---|---|---|
## Notes
- `,
classDiagram: `---
type: class_diagram
id: CLASSD-
name:
tags:
- Class
- Diagram
---
#
## Summary
## Objects
| ref | notes |
|---|---|
## Relations
| id | from | to | kind | label | from_multiplicity | to_multiplicity | notes |
|---|---|---|---|---|---|---|---|
## Notes
- `,
erEntity: `---
type: er_entity
id: ENT-
logical_name:
physical_name:
schema_name:
dbms:
tags:
- ER
- Entity
---
# /
## Overview
- purpose:
- notes:
## Columns
| logical_name | physical_name | data_type | length | scale | not_null | pk | encrypted | default_value | notes |
|---|---|---|---:|---:|---|---|---|---|---|
## Indexes
| index_name | index_type | unique | columns | notes |
|---|---|---|---|---|
## Relations
### REL-
- target_table: [[]]
- kind: fk
- cardinality:
- notes:
| local_column | target_column | notes |
|---|---|---|
## Notes
- `,
erDiagram: `---
type: er_diagram
id: ERD-
name:
tags:
- ER
- Diagram
---
#
## Summary
## Objects
| ref | notes |
|---|---|
## Notes
- `,
dfdObject: `---
type: dfd_object
id: DFD-
name:
kind: process
tags:
- DFD
---
#
## Summary
## Notes
`,
dfdDiagram: `---
type: dfd_diagram
id: DFD-
name:
level: 0
tags:
- DFD
- Diagram
---
#
## Summary
## Objects
| id | label | kind | ref | domain | notes |
|---|---|---|---|---|---|
| EXTERNAL | External System | external | | | Local object |
| PROCESS | Sample Process | process | [[DFD-PROC-SAMPLE]] | | Referenced reusable object |
| STORE | Sample Data Store | datastore | | | Local object |
## Flows
| id | from | to | data | notes |
|---|---|---|---|---|
| | | | | |
## Notes
- Add \`## Domain Sources\` only when referenced \`type: domains\` files already exist.
`,
flowDiagram: `---
type: flow_diagram
id: FLOW-
name:
kind: screen_communication
tags:
- FlowDiagram
- ScreenCommunication
---
#
## Summary
## Objects
| id | label | kind | ref | domain | notes |
|---|---|---|---|---|---|
| order_screen | Order Screen | screen | [[SCR-ORDER]] | | Source screen |
| order_process | Order Process | app_process | [[PROC-ORDER]] | | Application process |
| session_store | Session Store | session | | | Screen/session state |
| external_system | External System | external | | | External handoff target |
## Flows
| id | from | to | kind | trigger | data | condition | notes |
|---|---|---|---|---|---|---|---|
| FLOW-001 | order_screen | order_process | submit | click:Submit | [[DATA-ORDER-REQUEST]] | valid | Submit order request |
| FLOW-002 | order_process | session_store | context_update | | Order result | | Save result for display |
## Notes
`,
dataObject: `---
type: data_object
id:
name:
kind:
data_format: object
tags:
- DataObject
---
#
## Summary
## Fields
| name | label | type | length | required | path | ref | notes |
|---|---|---|---:|---|---|---|---|
| | | | | | | | |
## Notes
`,
dataObjectFileLayout: `---
type: data_object
id:
name:
kind: file
data_format:
encoding:
delimiter:
line_ending:
has_header:
record_length:
tags:
- DataObject
- File
---
#
## Summary
## Format
| key | value | notes |
|---|---|---|
| | | |
## Records
| record_type | name | occurrence | notes |
|---|---|---|---|
| | | | |
## Fields
| record_type | no | name | label | type | length | required | position | field_format | ref | notes |
|---|---:|---|---|---|---:|---|---|---|---|---|
| | | | | | | | | | | |
## Notes
`,
appProcess: `---
type: app_process
id: PROC-
name:
kind:
tags:
- AppProcess
---
#
## Summary
## Source Links
| path | notes |
|---|---|
| src/app/processes/ExampleProcess.ts | Example implementation |
## Triggers
| id | kind | source | event | notes |
|---|---|---|---|---|
| | | | | |
## Inputs
| id | data | source | required | notes |
|---|---|---|---|---|
| | | | | |
## Outputs
| id | data | target | notes |
|---|---|---|---|
| | | | |
## Domains
| id | name | kind | parent | description |
|---|---|---|---|---|
| user | User | external | | End user |
| system | System | application | | Application system |
| screen | Screen | application | | Screen/UI |
## Steps
| id | domain | label | kind | input | output | rule | invoke | screen | notes |
|---|---|---|---|---|---|---|---|---|---|
| step1 | user | Submit request | start | IN-REQUEST | | | | SCR-REQUEST | User starts the process |
| step2 | system | Validate request | process | IN-REQUEST | VALIDATED-REQUEST | RULE-VALIDATE | | | Check required values |
| step3 | screen | Show result | end | VALIDATED-REQUEST | OUT-RESULT | | | SCR-RESULT | Present the result |
## Flows
| from | to | condition | label | notes |
|---|---|---|---|---|
| step1 | step2 | | submit | |
| step2 | step3 | [[CODE-INVENTORY-STATUS]].available | show result | Flows.from/to are internal step ids; Flows.condition may contain structured references |
## Transitions
| id | event | to | condition | notes |
|---|---|---|---|---|
| | | | | |
## Errors
## Notes
`,
screen: `---
type: screen
id: SCR-
name:
screen_type:
tags:
- Screen
---
#
## Summary
## Layout
| id | label | kind | purpose | notes |
|---|---|---|---|---|
| | | | | |
## Fields
| id | label | kind | layout | data_type | required | ref | condition | rule | notes |
|---|---|---|---|---|---|---|---|---|---|
| | | | | | | | | | |
## Actions
| id | label | kind | target | event | condition | invoke | transition | rule | notes |
|---|---|---|---|---|---|---|---|---|---|
| | | | | | | | | | |
## Messages
| id | text | severity | timing | condition | notes |
|---|---|---|---|---|---|
| | | | | | |
## Transitions
| id | event | to | condition | notes |
|---|---|---|---|---|
| | | | | |
## Notes
## Local Processes
### PROC-CLEAR
#### Summary
#### Inputs
| id | data | source | required | notes |
|---|---|---|---|---|
| | | | | |
#### Steps
| id | label | kind | condition | input | output | rule | invoke | screen | notes |
|---|---|---|---|---|---|---|---|---|---|
| | | | | | | | | | |
#### Outputs
| id | data | target | notes |
|---|---|---|---|
| | | | |
#### Errors
| id | condition | message | notes |
|---|---|---|---|
| | | | |
`,
codeSet: `---
type: codeset
id:
name:
kind:
tags:
- CodeSet
---
#
## Summary
## Values
| code | label | sort_order | active | notes |
|---|---|---:|---|---|
## Notes
`,
message: `---
type: message
id:
name:
kind:
tags:
- Message
---
#
## Summary
## Messages
| message_id | text | severity | timing | audience | active | notes |
|---|---|---|---|---|---|---|
## Notes
`,
rule: `---
type: rule
id:
name:
kind:
tags:
- Rule
---
#
## Summary
## Inputs
| id | data | source | required | notes |
|---|---|---|---|---|
## References
| ref | usage | notes |
|---|---|---|
## Conditions
| id | condition | ref | value | notes |
|---|---|---|---|---|
| CND-001 | [[CODE-INVENTORY-STATUS]].available | [[CODE-INVENTORY-STATUS]] | available | \u826F\u54C1\u5229\u7528\u53EF\u306E\u5728\u5EAB\u306E\u307F\u5BFE\u8C61 |
Prose Conditions are human-readable. Table Conditions are analyzer-readable. \`condition\` may contain \`[[CODE-ID]].value\`; \`ref + value\` may also express a codeset value reference.
## Messages
| condition | message | severity | notes |
|---|---|---|---|
## Notes
`,
mapping: `---
type: mapping
id:
name:
kind:
source:
target:
tags:
- Mapping
---
#
## Summary
## Scope
| role | ref | notes |
|---|---|---|
## Mappings
| target_ref | source_ref | transform | rule | required | notes |
|---|---|---|---|---|---|
## Rules
## Notes
`,
domains: `---
type: domains
id: DOMAIN-SAMPLE
title: Domain Sample
---
# Domain Sample
## Domains
| id | name | kind | parent | description |
|---|---|---|---|---|
| business_domain | Business Domain | business | | Business capability area |
| application_domain | Application Domain | application | business_domain | Application capability area |
| data_domain | Data Domain | data | business_domain | Data management area |
| integration_domain | Integration Domain | integration | business_domain | External integration area |
## Notes
- \`kind\` is used by color_scheme when supported views apply colors.
- \`parent\` references another \`Domains.id\` in the same file.
- Domains Area/Tree views and Domain Diagram Area/Tree views can apply colors by kind.
`,
domainDiagram: `---
type: domain_diagram
id: DOMAIN-DIAGRAM-SAMPLE
title: Domain Diagram Sample
---
# Domain Diagram Sample
## Domain Sources
| ref |
|---|
| [[DOMAIN-SAMPLE]] |
## Notes
- Domain Diagram combines one or more \`domains\` files.
- Area/Tree views can use \`color_scheme\` for Domain kind colors.
- Add more rows to \`Domain Sources\` when combining multiple domain files.
`,
colorScheme: `---
type: color_scheme
id: COLOR-SCHEME-DEFAULT
name: DefaultColorScheme
tags:
- ColorScheme
---
# DefaultColorScheme
## Summary
Default color scheme for supported Model Weave views.
Set \`defaultColorSchemeRef\` to \`[[COLOR-SCHEME-DEFAULT]]\` in Model Weave settings to use this color scheme.
## Colors
| target | kind | fill | stroke | text | notes |
|---|---|---|---|---|---|
| | default | #f5f5f5 | #9e9e9e | #111111 | Global fallback |
| | business | #4f81bd | #2f5597 | #ffffff | Global business color |
| | application | #9bbb59 | #6f8a3f | #000000 | Global application color |
| | model | #8faadc | #5b7dbb | #000000 | Global model color |
| | renderer | #70ad47 | #507e32 | #000000 | Global renderer color |
| | data | #8064a2 | #60497a | #ffffff | Global data color |
| | integration | #f4b183 | #c55a11 | #000000 | Global integration color |
| | export | #ffd966 | #bf9000 | #000000 | Global export color |
| | ui | #76a5af | #45818e | #000000 | Global UI color |
| | operations | #7f7f7f | #595959 | #ffffff | Global operations color |
| | external | #bfbfbf | #7f7f7f | #000000 | Global external color |
| domain | default | #f5f5f5 | #9e9e9e | #111111 | Domain fallback |
| domain | business | #4f81bd | #2f5597 | #ffffff | Domain-specific business color |
| domain | application | #9bbb59 | #6f8a3f | #000000 | Domain-specific application color |
| domain | model | #8faadc | #5b7dbb | #000000 | Domain model color |
| domain | renderer | #70ad47 | #507e32 | #000000 | Domain renderer color |
| domain | data | #8064a2 | #60497a | #ffffff | Domain-specific data color |
| domain | integration | #f4b183 | #c55a11 | #000000 | Domain integration color |
| domain | export | #ffd966 | #bf9000 | #000000 | Domain export color |
| domain | ui | #76a5af | #45818e | #000000 | Domain UI color |
| domain | operations | #7f7f7f | #595959 | #ffffff | Domain operations color |
| domain | external | #bfbfbf | #7f7f7f | #000000 | Domain external color |
| dfd | default | #f5f5f5 | #9e9e9e | #111111 | DFD fallback |
| dfd | process | #9bbb59 | #6f8a3f | #000000 | DFD process |
| dfd | datastore | #8064a2 | #60497a | #ffffff | DFD datastore |
| dfd | external | #bfbfbf | #7f7f7f | #000000 | DFD external |
| dfd | other | #f4b183 | #c55a11 | #000000 | DFD other |
| app_process | default | #f5f5f5 | #9e9e9e | #111111 | Business Flow fallback |
| app_process | start | #4f81bd | #2f5597 | #ffffff | Start step |
| app_process | process | #9bbb59 | #6f8a3f | #000000 | Process step |
| app_process | decision | #f4b183 | #c55a11 | #000000 | Decision step |
| app_process | end | #bfbfbf | #7f7f7f | #000000 | End step |
## Notes
- Empty \`target\` means a global kind color.
- Target-specific rows override global rows for the same \`kind\`.
- Current runtime color application supports Domains Area/Tree, Domain Diagram Area/Tree, DFD, and app_process Business Flow.
- Mindmap is currently not colorized.
- Colors use HEX values.
- \`fill\` controls node background color.
- \`stroke\` controls node border color.
- \`text\` controls node text color.
- Do not define the same \`target + kind\` pair more than once.
`
};
var MODEL_WEAVE_RELATION_TEMPLATES = {
erRelationBlock: [
"### REL-",
"- target_table: [[]]",
"- kind: fk",
"- cardinality:",
"- notes:",
"",
"| local_column | target_column | notes |",
"|---|---|---|"
]
};
// src/types/enums.ts
var CORE_OBJECT_KINDS = [
"class",
"entity",
"interface",
"enum",
"component"
];
var RESERVED_OBJECT_KINDS = [
"actor",
"usecase"
];
var CORE_RELATION_KINDS = [
"association",
"dependency",
"composition",
"aggregation",
"inheritance",
"implementation",
"reference",
"flow"
];
var RESERVED_RELATION_KINDS = [
"include",
"extend",
"transition",
"message"
];
// src/parsers/object-parser.ts
var ATTRIBUTE_TABLE_HEADERS = [
"name",
"type",
"visibility",
"static",
"notes"
];
var METHOD_TABLE_HEADERS = [
"name",
"parameters",
"returns",
"visibility",
"static",
"notes"
];
var LEGACY_RELATION_TABLE_HEADERS = [
"id",
"from",
"to",
"kind",
"label",
"from_multiplicity",
"to_multiplicity",
"notes"
];
var SPEC04_RELATION_TABLE_HEADERS = [
"id",
"to",
"kind",
"label",
"from_multiplicity",
"to_multiplicity",
"notes"
];
function parseObjectFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const warnings = [...frontmatterResult.warnings];
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const schema = getString(frontmatter, "schema");
const type = getString(frontmatter, "type");
const acceptsClassType = type === "class";
if (detectFileType(frontmatter) !== "object" || !acceptsClassType && schema !== "model_object_v1") {
warnings.push(
createWarning5(
"unknown-schema",
`object parser expected schema "model_object_v1" or type "class" but received schema "${schema ?? "none"}" / type "${type ?? "none"}"`,
path2,
acceptsClassType ? "type" : "schema"
)
);
return {
file: null,
warnings
};
}
const sections = extractMarkdownSections(frontmatterResult.file.body);
const name = getString(frontmatter, "name");
const rawKind = getString(frontmatter, "kind") ?? (acceptsClassType ? "class" : void 0);
const summary = joinSectionLines(sections.Summary);
const attributes = acceptsClassType ? parseAttributeTable(sections.Attributes, warnings, path2) : parseAttributes(sections.Attributes, warnings, path2);
const methods = acceptsClassType ? parseMethodTable(sections.Methods, warnings, path2) : parseMethods(sections.Methods, warnings, path2);
const relations = parseRelationsTable(
sections.Relations,
warnings,
path2,
getClassObjectId(frontmatter, name)
);
if (!name) {
warnings.push(
createWarning5("missing-name", 'missing required field "name"', path2, "name")
);
}
if (!rawKind) {
warnings.push(
createWarning5("missing-kind", 'missing required field "kind"', path2, "kind")
);
} else if (isReservedObjectKind(rawKind)) {
warnings.push(
createInfoWarning2(
"reserved-kind-used",
`reserved kind used: "${rawKind}"`,
path2,
"kind"
)
);
} else if (!isCoreObjectKind(rawKind)) {
warnings.push(
createWarning5("invalid-kind", `invalid kind "${rawKind}"`, path2, "kind")
);
}
const file = {
fileType: "object",
schema: "model_object_v1",
path: path2,
title: getString(frontmatter, "title"),
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
name: name ?? getString(frontmatter, "id") ?? "unknown",
kind: normalizeObjectKind(rawKind),
description: summary || void 0,
attributes,
methods,
relations
};
return {
file,
warnings
};
}
function parseAttributes(lines, warnings, path2) {
if (!lines) {
return [];
}
const attributes = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const match = trimmed.match(/^-\s+([^:]+?)\s*:\s*(.+?)(?:\s+-\s+(.+))?$/);
if (!match) {
warnings.push(
createWarning5(
"invalid-attribute-line",
`malformed attribute line: "${trimmed}"`,
path2,
"Attributes"
)
);
continue;
}
const [, name, type, note] = match;
attributes.push({
name: name.trim(),
type: type.trim(),
description: note?.trim(),
raw: trimmed
});
}
return attributes;
}
function parseMethods(lines, warnings, path2) {
if (!lines) {
return [];
}
const methods = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const match = trimmed.match(
/^-\s+([A-Za-z_][\w]*)\(([^)]*)\)\s+([^-].*?)(?:\s+-\s+(.+))?$/
);
if (!match) {
warnings.push(
createWarning5(
"invalid-method-line",
`malformed method line: "${trimmed}"`,
path2,
"Methods"
)
);
continue;
}
const [, name, rawParameters, rawReturnType, note] = match;
methods.push({
name,
parameters: parseMethodParameters(rawParameters),
returnType: rawReturnType.trim(),
description: note?.trim(),
raw: trimmed
});
}
return methods;
}
function parseMethodParameters(rawParameters) {
const trimmed = rawParameters.trim();
if (!trimmed) {
return [];
}
return trimmed.split(",").map((parameter) => {
const value = parameter.trim();
const match = value.match(/^([A-Za-z_][\w]*)(\?)?\s*:\s*(.+)$/);
if (!match) {
return {
name: value,
required: true
};
}
const [, name, optionalFlag, type] = match;
return {
name,
type: type.trim(),
required: optionalFlag !== "?"
};
});
}
function parseAttributeTable(lines, warnings, path2) {
const table = parseMarkdownTable(
lines,
[...ATTRIBUTE_TABLE_HEADERS],
path2,
"Attributes"
);
warnings.push(...table.warnings);
return table.rows.map((row) => ({
name: getTableValue(row, "name"),
type: optionalTableValue(row, "type"),
visibility: normalizeVisibility(optionalTableValue(row, "visibility")),
description: optionalTableValue(row, "notes"),
raw: JSON.stringify(row)
}));
}
function parseMethodTable(lines, warnings, path2) {
const table = parseMarkdownTable(
lines,
[...METHOD_TABLE_HEADERS],
path2,
"Methods"
);
warnings.push(...table.warnings);
return table.rows.map((row) => ({
name: getTableValue(row, "name"),
parameters: parseMethodParameters(getTableValue(row, "parameters")),
returnType: optionalTableValue(row, "returns"),
visibility: normalizeVisibility(optionalTableValue(row, "visibility")),
isStatic: normalizeBoolean(optionalTableValue(row, "static")),
description: optionalTableValue(row, "notes"),
raw: JSON.stringify(row)
}));
}
function parseRelationsTable(lines, warnings, path2, currentClassId) {
const relations = [];
const table = parseClassRelationsTable(lines, path2);
warnings.push(...table.warnings);
for (const row of table.rows) {
const id = getTableValue(row, "id");
const to = normalizeReferenceTarget(getTableValue(row, "to"));
const kind = getTableValue(row, "kind");
const from = table.format === "legacy" ? normalizeReferenceTarget(getTableValue(row, "from")) : normalizeReferenceTarget(currentClassId);
if (!id || !from || !to || !kind) {
warnings.push(
createWarning5(
"invalid-table-row",
`Relations row is missing required values: ${JSON.stringify(row)}`,
path2,
"Relations"
)
);
continue;
}
if (table.format === "legacy") {
if (from === normalizeReferenceTarget(currentClassId)) {
warnings.push(
createInfoWarning2(
"legacy-class-relation-format",
`Legacy class relation format with explicit "from" was accepted for relation "${id}".`,
path2,
"Relations"
)
);
} else {
warnings.push(
createWarning5(
"legacy-class-relation-from-mismatch",
`Legacy class relation "from" does not match the current class id for relation "${id}".`,
path2,
"Relations"
)
);
}
}
relations.push({
domain: "class",
id,
source: from,
target: to,
sourceClass: from,
targetClass: to,
kind,
label: optionalTableValue(row, "label"),
fromMultiplicity: optionalTableValue(row, "from_multiplicity"),
toMultiplicity: optionalTableValue(row, "to_multiplicity"),
notes: optionalTableValue(row, "notes")
});
}
return relations;
}
function parseClassRelationsTable(lines, path2) {
if (!lines) {
return { rows: [], warnings: [], format: "spec04" };
}
const normalizedLines = lines.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (normalizedLines.length < 2) {
return {
rows: [],
warnings: normalizedLines.length === 0 ? [] : [
createWarning5(
"invalid-table-row",
'table in section "Relations" is incomplete',
path2,
"Relations"
)
],
format: "spec04"
};
}
const headers = splitMarkdownTableRow(normalizedLines[0]) ?? [];
const format = sameHeaders2(headers, [...SPEC04_RELATION_TABLE_HEADERS]) ? "spec04" : sameHeaders2(headers, [...LEGACY_RELATION_TABLE_HEADERS]) ? "legacy" : "spec04";
const warnings = [];
if (!sameHeaders2(headers, [...SPEC04_RELATION_TABLE_HEADERS]) && !sameHeaders2(headers, [...LEGACY_RELATION_TABLE_HEADERS])) {
warnings.push(
createWarning5(
"invalid-table-column",
'table columns in section "Relations" do not match supported class relation headers',
path2,
"Relations"
)
);
}
const rows = [];
for (const rowLine of normalizedLines.slice(2)) {
const values = splitMarkdownTableRow(rowLine) ?? [];
if (isEmptyMarkdownTableDataRow(values)) {
continue;
}
if (values.length !== headers.length) {
warnings.push(
createWarning5(
"invalid-table-row",
`table row in section "Relations" has ${values.length} columns, expected ${headers.length}`,
path2,
"Relations"
)
);
continue;
}
const row = {};
for (const [index, header] of headers.entries()) {
row[header] = values[index] ?? "";
}
rows.push(row);
}
return { rows, warnings, format };
}
function sameHeaders2(actual, expected) {
if (actual.length !== expected.length) {
return false;
}
return actual.every((header, index) => header === expected[index]);
}
function getTableValue(row, key) {
return row[key]?.trim() ?? "";
}
function optionalTableValue(row, key) {
const value = getTableValue(row, key);
return value || void 0;
}
function normalizeVisibility(value) {
switch (value) {
case "public":
case "protected":
case "private":
case "package":
return value;
default:
return void 0;
}
}
function normalizeBoolean(value) {
if (!value) {
return void 0;
}
const normalized = value.trim().toLowerCase();
if (normalized === "true" || normalized === "y" || normalized === "yes") {
return true;
}
if (normalized === "false" || normalized === "n" || normalized === "no") {
return false;
}
return void 0;
}
function joinSectionLines(lines) {
if (!lines) {
return "";
}
return lines.map((line) => line.trim()).filter(Boolean).join("\n");
}
function getString(frontmatter, key) {
const value = frontmatter[key];
return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function normalizeObjectKind(kind) {
if (kind && (isCoreObjectKind(kind) || isReservedObjectKind(kind))) {
return kind;
}
return "class";
}
function getClassObjectId(frontmatter, name) {
return getString(frontmatter, "id") ?? name ?? "unknown";
}
function isCoreObjectKind(kind) {
return CORE_OBJECT_KINDS.some((candidate) => candidate === kind);
}
function isReservedObjectKind(kind) {
return RESERVED_OBJECT_KINDS.some((candidate) => candidate === kind);
}
function createWarning5(code, message, path2, field) {
return {
code,
message,
severity: "warning",
path: path2,
field
};
}
function createInfoWarning2(code, message, path2, field) {
return {
code,
message,
severity: "info",
path: path2,
field
};
}
// src/parsers/relations-parser.ts
function parseRelationsFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const warnings = [...frontmatterResult.warnings];
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const schema = getString2(frontmatter, "schema");
if (detectFileType(frontmatter) !== "relations" || schema !== "model_relations_v1") {
warnings.push(
createWarning6(
"unknown-schema",
`relations parser expected schema "model_relations_v1" but received "${schema ?? "none"}"`,
path2,
"schema"
)
);
return {
file: null,
warnings
};
}
const sections = extractMarkdownSections(frontmatterResult.file.body);
if (!sections.Relations) {
warnings.push(
createInfoWarning3(
"section-missing",
'section missing: "Relations"',
path2,
"Relations"
)
);
}
const relations = parseRelationsSection(sections.Relations, warnings, path2);
return {
file: {
fileType: "relations",
schema: "model_relations_v1",
path: path2,
title: getString2(frontmatter, "title"),
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
relations
},
warnings
};
}
function parseRelationsSection(lines, warnings, path2) {
if (!lines) {
return [];
}
const relations = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const record = parseRelationRecord(trimmed);
if (!record) {
warnings.push(
createWarning6(
"invalid-relation-record",
`malformed relation record: "${trimmed}"`,
path2,
"Relations"
)
);
continue;
}
const missingFields = ["id", "from", "to", "kind"].filter(
(field) => !record[field]
);
if (missingFields.length > 0) {
warnings.push(
createWarning6(
"invalid-relation-record",
`malformed relation record: missing ${missingFields.join(", ")}`,
path2,
"Relations"
)
);
continue;
}
const rawKind = record.kind;
if (isReservedRelationKind(rawKind)) {
warnings.push(
createInfoWarning3(
"reserved-relation-kind-used",
`reserved kind used: "${rawKind}"`,
path2,
"kind"
)
);
} else if (!isCoreRelationKind(rawKind)) {
warnings.push(
createWarning6(
"invalid-relation-kind",
`invalid relation kind "${rawKind}"`,
path2,
"kind"
)
);
}
relations.push({
id: record.id,
source: record.from,
target: record.to,
kind: normalizeRelationKind(rawKind),
label: typeof record.label === "string" ? record.label : void 0,
sourceCardinality: typeof record.from_multiplicity === "string" ? record.from_multiplicity : void 0,
targetCardinality: typeof record.to_multiplicity === "string" ? record.to_multiplicity : void 0,
metadata: {
raw: trimmed
}
});
}
return relations;
}
function parseRelationRecord(line) {
const bulletMatch = line.match(/^-\s+(.+)$/);
if (!bulletMatch) {
return null;
}
const record = {};
for (const part of bulletMatch[1].split(",")) {
const segment = part.trim();
if (!segment) {
continue;
}
const match = segment.match(/^([A-Za-z_][\w]*)\s*:\s*(.+)$/);
if (!match) {
return null;
}
const [, key, value] = match;
record[key] = value.trim();
}
return record;
}
function getString2(frontmatter, key) {
const value = frontmatter[key];
return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function isCoreRelationKind(kind) {
return CORE_RELATION_KINDS.some((candidate) => candidate === kind);
}
function isReservedRelationKind(kind) {
return RESERVED_RELATION_KINDS.some((candidate) => candidate === kind);
}
function normalizeRelationKind(kind) {
if (isCoreRelationKind(kind) || isReservedRelationKind(kind)) {
return kind;
}
return "association";
}
function createWarning6(code, message, path2, field) {
return {
code,
message,
severity: "warning",
path: path2,
field
};
}
function createInfoWarning3(code, message, path2, field) {
return {
code,
message,
severity: "info",
path: path2,
field
};
}
// src/parsers/diagram-parser.ts
var ER_DIAGRAM_OBJECT_HEADERS = ["ref", "notes"];
var CLASS_DIAGRAM_OBJECT_HEADERS = ["ref", "notes"];
var CLASS_DIAGRAM_RELATION_HEADERS = [
"id",
"from",
"to",
"kind",
"label",
"from_multiplicity",
"to_multiplicity",
"notes"
];
function parseDiagramFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const warnings = [...frontmatterResult.warnings];
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const type = getString3(frontmatter, "type");
const acceptsErDiagramType = type === "er_diagram";
const acceptsClassDiagramType = type === "class_diagram";
if (detectFileType(frontmatter) !== "diagram" || !acceptsErDiagramType && !acceptsClassDiagramType) {
warnings.push(
createWarning7(
"unknown-schema",
`diagram parser expected type "er_diagram" or "class_diagram" but received type "${type ?? "none"}"`,
path2,
"type"
)
);
return {
file: null,
warnings
};
}
const sections = extractMarkdownSections(frontmatterResult.file.body);
const name = getString3(frontmatter, "name");
const objectRows = acceptsErDiagramType ? parseErDiagramObjects(sections.Objects, warnings, path2) : acceptsClassDiagramType ? parseClassDiagramObjects(sections.Objects, warnings, path2) : null;
const objectRefs = objectRows ? objectRows.map((row) => row.ref) : [];
const classDiagramRelations = acceptsClassDiagramType ? parseClassDiagramRelations(sections.Relations, warnings, path2) : [];
const nodes = objectRows ? objectRows.map(
(row) => ({
id: row.ref,
ref: row.ref,
metadata: row.notes ? { notes: row.notes } : void 0
})
) : objectRefs.map((ref) => ({
id: normalizeReferenceTarget(ref),
ref
}));
if (!name) {
warnings.push(
createWarning7("missing-name", 'missing required field "name"', path2, "name")
);
}
if (!sections.Objects) {
warnings.push(
createInfoWarning4(
"section-missing",
'section missing: "Objects"',
path2,
"Objects"
)
);
}
return {
file: {
fileType: "diagram",
schema: acceptsErDiagramType ? "er_diagram" : "class_diagram",
path: path2,
title: getString3(frontmatter, "title"),
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
name: name ?? getString3(frontmatter, "id") ?? "unknown",
kind: acceptsErDiagramType ? "er" : "class",
objectRefs,
nodes,
edges: acceptsClassDiagramType ? classDiagramRelations.map(classRelationToDiagramEdge) : []
},
warnings
};
}
function parseErDiagramObjects(lines, warnings, path2) {
const table = parseMarkdownTable(
lines,
[...ER_DIAGRAM_OBJECT_HEADERS],
path2,
"Objects"
);
warnings.push(...table.warnings);
const objects = [];
for (const row of table.rows) {
const ref = row.ref?.trim();
if (!ref) {
warnings.push(
createWarning7(
"invalid-object-ref",
'table row in section "Objects" is missing "ref"',
path2,
"Objects"
)
);
continue;
}
const notes = row.notes?.trim();
objects.push({
ref,
notes: notes ? notes : void 0
});
}
return objects;
}
function parseClassDiagramObjects(lines, warnings, path2) {
const table = parseMarkdownTable(
lines,
[...CLASS_DIAGRAM_OBJECT_HEADERS],
path2,
"Objects"
);
warnings.push(...table.warnings);
const objects = [];
for (const row of table.rows) {
const rawRef = row.ref?.trim();
if (!rawRef) {
warnings.push(
createWarning7(
"invalid-object-ref",
'table row in section "Objects" is missing "ref"',
path2,
"Objects"
)
);
continue;
}
objects.push({
ref: normalizeReferenceTarget(rawRef),
notes: row.notes?.trim() || void 0
});
}
return objects;
}
function parseClassDiagramRelations(lines, warnings, path2) {
if (!hasNonEmptyTableDataRows(lines)) {
return [];
}
const table = parseMarkdownTable(
lines,
[...CLASS_DIAGRAM_RELATION_HEADERS],
path2,
"Relations"
);
warnings.push(...table.warnings);
const relations = [];
for (const row of table.rows) {
const id = row.id?.trim();
const from = normalizeReferenceTarget(row.from?.trim() ?? "");
const to = normalizeReferenceTarget(row.to?.trim() ?? "");
const kind = row.kind?.trim();
if (!id || !from || !to || !kind) {
warnings.push(
createWarning7(
"invalid-table-row",
`table row in section "Relations" is missing required values`,
path2,
"Relations"
)
);
continue;
}
relations.push({
domain: "class",
id,
source: from,
target: to,
sourceClass: from,
targetClass: to,
kind,
label: row.label?.trim() || void 0,
fromMultiplicity: row.from_multiplicity?.trim() || void 0,
toMultiplicity: row.to_multiplicity?.trim() || void 0,
notes: row.notes?.trim() || void 0
});
}
return relations;
}
function hasNonEmptyTableDataRows(lines) {
if (!lines) {
return false;
}
const tableLines = lines.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (tableLines.length <= 2) {
return false;
}
return tableLines.slice(2).some(
(line) => line.replace(/^\|/, "").replace(/\|$/, "").split("|").some((cell) => cell.trim().length > 0)
);
}
function classRelationToDiagramEdge(relation) {
return {
id: relation.id,
source: relation.sourceClass,
target: relation.targetClass,
kind: relation.kind,
label: relation.label,
metadata: {
notes: relation.notes,
sourceCardinality: relation.fromMultiplicity,
targetCardinality: relation.toMultiplicity
}
};
}
function getString3(frontmatter, key) {
const value = frontmatter[key];
return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function createWarning7(code, message, path2, field) {
return {
code,
message,
severity: "warning",
path: path2,
field
};
}
function createInfoWarning4(code, message, path2, field) {
return {
code,
message,
severity: "info",
path: path2,
field
};
}
// src/parsers/domain-sources-parser.ts
var DOMAIN_SOURCE_HEADERS = ["ref"];
var DOMAIN_SOURCE_WITH_NOTES_HEADERS = ["ref", "notes"];
function parseDomainSourcesTable(lines, path2) {
if (!lines) {
return { rows: [], warnings: [] };
}
const normalizedLines = lines.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (normalizedLines.length < 2) {
return {
rows: [],
warnings: normalizedLines.length === 0 ? [] : [createTableWarning(
"invalid-table-row",
path2,
"Domain Sources",
'table in section "Domain Sources" is incomplete'
)]
};
}
const headers = splitMarkdownTableRow(normalizedLines[0]) ?? [];
const warnings = [];
if (!isSupportedDomainSourceHeaders(headers)) {
warnings.push(
createTableWarning(
"invalid-table-column",
path2,
"Domain Sources",
'table columns in section "Domain Sources" do not match supported Domain Sources headers'
)
);
}
const rows = [];
normalizedLines.slice(2).forEach((rowLine, rowIndex) => {
const values = splitMarkdownTableRow(rowLine) ?? [];
if (isEmptyMarkdownTableDataRow(values)) {
return;
}
if (values.length !== headers.length) {
warnings.push(
createTableWarning(
"invalid-table-row",
path2,
"Domain Sources",
`table row in section "Domain Sources" has ${values.length} columns, expected ${headers.length}`
)
);
return;
}
const row = {};
for (const [index, header] of headers.entries()) {
row[header] = values[index] ?? "";
}
const ref = row.ref?.trim() ?? "";
if (!ref) {
warnings.push({
code: "invalid-structure",
message: formatDomainDiagramMissingRefMessage(),
severity: "error",
path: path2,
field: "Domain Sources.ref",
context: { rowIndex: rowIndex + 1 }
});
return;
}
rows.push({
ref,
notes: row.notes?.trim() || void 0,
rowIndex
});
});
return { rows, warnings };
}
function isSupportedDomainSourceHeaders(headers) {
return sameHeaders3(headers, DOMAIN_SOURCE_HEADERS) || sameHeaders3(headers, DOMAIN_SOURCE_WITH_NOTES_HEADERS);
}
function sameHeaders3(actual, expected) {
return actual.length === expected.length && actual.every((header, index) => header === expected[index]);
}
function createTableWarning(code, path2, field, message) {
return {
code,
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/dfd-diagram-parser.ts
var DFD_FLOW_HEADERS = ["id", "from", "to", "data", "notes"];
var FLOW_DIAGRAM_FLOW_HEADERS = ["id", "from", "to", "kind", "trigger", "data", "condition", "notes"];
var OBJECT_HEADERS = ["id", "label", "kind", "ref", "domain", "notes"];
var DFD_OBJECT_HEADERS_WITHOUT_DOMAIN = ["id", "label", "kind", "ref", "notes"];
var LEGACY_OBJECT_HEADERS = ["ref", "notes"];
var FLOW_OBJECT_KINDS = /* @__PURE__ */ new Set([
"screen",
"actor",
"user",
"message",
"data",
"api",
"service",
"handler",
"process",
"app_process",
"context",
"work_object",
"session",
"store",
"datastore",
"external",
"unknown"
]);
function parseDfdDiagramFile(markdown, path2) {
return parseDfdLikeDiagramFile(markdown, path2, {
type: "dfd_diagram",
fileType: "dfd-diagram",
schema: "dfd_diagram",
defaultTitle: "Untitled DFD Diagram",
defaultKind: "dfd",
allowLegacyObjects: true,
requireObjectId: false
});
}
function parseFlowDiagramFile(markdown, path2) {
return parseDfdLikeDiagramFile(markdown, path2, {
type: "flow_diagram",
fileType: "flow-diagram",
schema: "flow_diagram",
defaultTitle: "Untitled Flow Diagram",
defaultKind: "screen_communication",
allowLegacyObjects: false,
requireObjectId: true
});
}
function parseDfdLikeDiagramFile(markdown, path2, options) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const level = typeof frontmatter.level === "string" || typeof frontmatter.level === "number" ? String(frontmatter.level).trim() : void 0;
const kind = typeof frontmatter.kind === "string" && frontmatter.kind.trim() ? frontmatter.kind.trim() : options.defaultKind;
const flowView = parseFlowDiagramViewMode(frontmatter.flow_view);
const flowViewRaw = frontmatter.flow_view;
const flowViewSpecified = options.schema === "flow_diagram" && !isUnknownFlowDiagramViewMode(frontmatter.flow_view) && typeof frontmatter.flow_view === "string" && frontmatter.flow_view.trim().length > 0;
const rawType = typeof frontmatter.type === "string" ? frontmatter.type.trim() : "";
const isAcceptedType = rawType === options.type || options.type === "flow_diagram" && rawType === "flow-diagram";
if (!isAcceptedType) {
warnings.push(createWarning8(path2, "type", `expected type "${options.type}"`));
}
if (!id) {
warnings.push(createWarning8(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning8(path2, "name", 'required frontmatter "name" is missing'));
}
if (options.schema === "flow_diagram" && kind !== "screen_communication") {
warnings.push(createWarning8(path2, "kind", 'expected kind "screen_communication"'));
}
if (options.schema === "flow_diagram" && isUnknownFlowDiagramViewMode(frontmatter.flow_view)) {
warnings.push(createWarning8(path2, "flow_view", 'unknown flow_view; expected "detail" or "screen"'));
}
const objectsTable = parseDfdObjectsTable(sections.Objects, path2, {
schema: options.schema,
allowLegacyObjects: options.allowLegacyObjects,
requireObjectId: options.requireObjectId
});
const domainsTable = parseDomainEntries(sections.Domains, path2);
const domainSourcesTable = parseDomainSourcesTable(sections["Domain Sources"], path2);
const flowHeaders = options.schema === "flow_diagram" ? FLOW_DIAGRAM_FLOW_HEADERS : DFD_FLOW_HEADERS;
const flowsTable = parseMarkdownTable(sections.Flows, flowHeaders, path2, "Flows");
const hasInvalidFlowsHeader = flowsTable.warnings.some(
(warning) => warning.code === "invalid-table-column"
);
warnings.push(
...domainsTable.warnings,
...validateDomainEntries(path2, domainsTable.rows, {
skipUnknownParents: domainSourcesTable.rows.length > 0
}),
...domainSourcesTable.warnings,
...objectsTable.warnings,
...flowsTable.warnings
);
const fallbackTitle = name || id || getFileStem4(path2) || options.defaultTitle;
const objectEntries = objectsTable.rows;
const objectRefs = objectEntries.map((row) => row.id?.trim() || row.ref?.trim() || "").filter(Boolean);
const nodes = objectEntries.map((entry) => ({
id: entry.id?.trim() || entry.ref?.trim() || `object-${entry.rowIndex + 1}`,
ref: entry.ref?.trim() || void 0,
label: entry.label?.trim() || void 0,
kind: entry.kind,
metadata: {
domain: entry.domain,
rowIndex: entry.rowIndex
}
}));
const flows = [];
const edges = [];
if (!hasInvalidFlowsHeader) {
flowsTable.rows.forEach((row, rowIndex) => {
const from = row.from?.trim() ?? "";
const to = row.to?.trim() ?? "";
const flowKind = options.schema === "flow_diagram" ? row.kind?.trim() ?? "" : "";
const trigger = options.schema === "flow_diagram" ? row.trigger?.trim() ?? "" : "";
const data = row.data?.trim() ?? "";
const condition = options.schema === "flow_diagram" ? row.condition?.trim() ?? "" : "";
const notes = row.notes?.trim() ?? "";
const flowId = row.id?.trim() ?? "";
if (!flowId) {
warnings.push({
code: "invalid-structure",
message: `${options.schema === "flow_diagram" ? "Flow Diagram" : "DFD"} Flows row must have "id".`,
severity: "error",
path: path2,
field: "Flows",
context: { rowIndex: rowIndex + 1 }
});
}
if (!from) {
warnings.push({
code: "invalid-structure",
message: `${options.schema === "flow_diagram" ? "Flow Diagram" : "DFD"} Flows row must have "from".`,
severity: "error",
path: path2,
field: "Flows",
context: { rowIndex: rowIndex + 1 }
});
}
if (!to) {
warnings.push({
code: "invalid-structure",
message: `${options.schema === "flow_diagram" ? "Flow Diagram" : "DFD"} Flows row must have "to".`,
severity: "error",
path: path2,
field: "Flows",
context: { rowIndex: rowIndex + 1 }
});
}
flows.push({
id: flowId || void 0,
from,
to,
kind: flowKind || void 0,
trigger: trigger || void 0,
data: data || void 0,
dataRef: data ? parseReferenceValue(data) ?? void 0 : void 0,
condition: condition || void 0,
notes: notes || void 0,
rowIndex
});
edges.push({
id: flowId || void 0,
source: from,
target: to,
kind: "flow",
label: data || void 0,
metadata: {
notes: notes || void 0,
flowKind: flowKind || void 0,
trigger: trigger || void 0,
dataRaw: data || void 0,
condition: condition || void 0,
rowIndex
}
});
});
}
if (options.schema === "flow_diagram") {
return {
file: {
fileType: "flow-diagram",
schema: "flow_diagram",
path: path2,
title: fallbackTitle,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: name || fallbackTitle,
kind: "screen_communication",
description: joinSectionLines2(sections.Summary),
flowView,
flowViewSpecified,
flowViewRaw,
domainSources: domainSourcesTable.rows,
domains: domainsTable.rows,
objectRefs,
objectEntries,
nodes,
edges,
flows
},
warnings
};
}
return {
file: {
fileType: "dfd-diagram",
schema: "dfd_diagram",
path: path2,
title: fallbackTitle,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: name || fallbackTitle,
kind: "dfd",
level,
description: joinSectionLines2(sections.Summary),
domainSources: domainSourcesTable.rows,
domains: domainsTable.rows,
objectRefs,
objectEntries,
nodes,
edges,
flows
},
warnings
};
}
function parseFlowDiagramViewMode(value) {
return typeof value === "string" && value.trim() === "screen" ? "screen" : "detail";
}
function isUnknownFlowDiagramViewMode(value) {
return typeof value === "string" && value.trim().length > 0 && value.trim() !== "detail" && value.trim() !== "screen";
}
function getFileStem4(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines2(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function createWarning8(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
function createTableWarning2(path2, field, message) {
return {
code: "invalid-table-column",
message,
severity: "warning",
path: path2,
field
};
}
function parseDfdObjectsTable(lines, path2, options) {
if (!lines) {
return { rows: [], warnings: [] };
}
const normalizedLines = lines.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (normalizedLines.length < 2) {
return {
rows: [],
warnings: normalizedLines.length === 0 ? [] : [{
code: "invalid-table-row",
message: 'table in section "Objects" is incomplete',
severity: "warning",
path: path2,
field: "Objects"
}]
};
}
const headers = splitMarkdownTableRow(normalizedLines[0]) ?? [];
const warnings = [];
const hasLegacyHeaders = options.allowLegacyObjects && sameHeaders4(headers, LEGACY_OBJECT_HEADERS);
const hasLocalHeaders = sameHeaders4(headers, OBJECT_HEADERS) || options.schema === "dfd_diagram" && sameHeaders4(headers, DFD_OBJECT_HEADERS_WITHOUT_DOMAIN);
if (!hasLegacyHeaders && !hasLocalHeaders) {
warnings.push(
createTableWarning2(
path2,
"Objects",
options.schema === "flow_diagram" ? 'table columns in section "Objects" do not match expected headers' : 'table columns in section "Objects" do not match supported DFD object headers'
)
);
return { rows: [], warnings };
}
if (hasLegacyHeaders) {
warnings.push({
code: "invalid-structure",
message: "Old ref-only DFD Objects format detected; compatibility mode used.",
severity: "info",
path: path2,
field: "Objects"
});
}
const rows = [];
const seenIds = /* @__PURE__ */ new Set();
normalizedLines.slice(2).forEach((rowLine, rowIndex) => {
const values = splitMarkdownTableRow(rowLine) ?? [];
if (isEmptyMarkdownTableDataRow(values)) {
return;
}
if (values.length !== headers.length) {
warnings.push({
code: "invalid-table-row",
message: `table row in section "Objects" has ${values.length} columns, expected ${headers.length}`,
severity: "warning",
path: path2,
field: "Objects"
});
return;
}
const row = {};
for (const [headerIndex, header] of headers.entries()) {
row[header] = values[headerIndex] ?? "";
}
const id = row.id?.trim() || "";
const label = row.label?.trim() || "";
const kind = row.kind?.trim() || "";
const ref = row.ref?.trim() || "";
const domain = row.domain?.trim() || "";
const notes = row.notes?.trim() || "";
if (options.requireObjectId ? !id : !id && !ref) {
warnings.push({
code: "invalid-structure",
message: options.schema === "flow_diagram" ? 'Flow Diagram Objects row must have "id".' : 'DFD Objects row must have "id" or "ref".',
severity: "error",
path: path2,
field: "Objects",
context: { rowIndex: rowIndex + 1 }
});
return;
}
if (id) {
if (seenIds.has(id)) {
warnings.push({
code: "invalid-structure",
message: `duplicate ${options.schema === "flow_diagram" ? "Flow Diagram" : "DFD"} Objects.id "${id}"`,
severity: "error",
path: path2,
field: "Objects",
context: { rowIndex: rowIndex + 1 }
});
} else {
seenIds.add(id);
}
}
if (kind && options.schema === "dfd_diagram" && !isSupportedDfdDiagramObjectKind(kind)) {
warnings.push({
code: "invalid-structure",
message: `unknown DFD object kind "${kind}"`,
severity: "warning",
path: path2,
field: "Objects",
context: { rowIndex: rowIndex + 1 }
});
}
rows.push({
id: id || void 0,
label: label || void 0,
kind: kind ? normalizeDiagramObjectKind(kind, options.schema) : void 0,
ref: ref || void 0,
domain: domain || void 0,
notes: notes || void 0,
rowIndex,
compatibilityMode: hasLegacyHeaders ? "legacy_ref_only" : "explicit"
});
});
return { rows, warnings };
}
function normalizeDiagramObjectKind(value, schema) {
if (schema === "flow_diagram") {
return FLOW_OBJECT_KINDS.has(value) ? value : "unknown";
}
return normalizeDfdDiagramObjectKind(value);
}
function normalizeDfdDiagramObjectKind(value) {
switch (value) {
case "external":
case "process":
case "datastore":
return value;
default:
return "other";
}
}
function isSupportedDfdDiagramObjectKind(value) {
return value === "external" || value === "process" || value === "datastore" || value === "other";
}
function sameHeaders4(actual, expected) {
return actual.length === expected.length && actual.every((header, index) => header === expected[index]);
}
// src/parsers/dfd-object-parser.ts
var DFD_OBJECT_KINDS = /* @__PURE__ */ new Set(["external", "process", "datastore"]);
function parseDfdObjectFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const rawKind = typeof frontmatter.kind === "string" ? frontmatter.kind.trim() : "";
if (frontmatter.type !== "dfd_object") {
warnings.push(createWarning9(path2, "type", 'expected type "dfd_object"'));
}
if (!id) {
warnings.push(createWarning9(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning9(path2, "name", 'required frontmatter "name" is missing'));
}
if (!rawKind) {
warnings.push(createWarning9(path2, "kind", 'required frontmatter "kind" is missing'));
} else if (!DFD_OBJECT_KINDS.has(rawKind)) {
warnings.push({
code: "invalid-kind",
message: `invalid dfd_object kind "${rawKind}"`,
severity: "warning",
path: path2,
field: "kind"
});
}
const fallbackName = name || id || getFileStem5(path2) || "Untitled DFD Object";
const normalizedKind = DFD_OBJECT_KINDS.has(rawKind) ? rawKind : "process";
return {
file: {
fileType: "dfd-object",
schema: "dfd_object",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackName,
kind: normalizedKind,
summary: joinSectionLines3(sections.Summary),
notes: normalizeNotes(sections.Notes)
},
warnings
};
}
function getFileStem5(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines3(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function createWarning9(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/data-object-parser.ts
var FORMAT_HEADERS = ["key", "value", "notes"];
var RECORD_HEADERS = ["record_type", "name", "occurrence", "notes"];
var FILE_LAYOUT_HINTS = /* @__PURE__ */ new Set(["record_type", "no", "position", "field_format"]);
function parseDataObjectFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const kind = typeof frontmatter.kind === "string" ? frontmatter.kind.trim() : "";
const dataFormat = typeof frontmatter.data_format === "string" ? frontmatter.data_format.trim() : "";
const encoding = typeof frontmatter.encoding === "string" ? frontmatter.encoding.trim() : "";
const delimiter = typeof frontmatter.delimiter === "string" ? frontmatter.delimiter.trim() : "";
const lineEnding = typeof frontmatter.line_ending === "string" ? frontmatter.line_ending.trim() : "";
const hasHeader = typeof frontmatter.has_header === "string" || typeof frontmatter.has_header === "boolean" ? String(frontmatter.has_header).trim() : "";
const recordLength = typeof frontmatter.record_length === "string" || typeof frontmatter.record_length === "number" ? String(frontmatter.record_length).trim() : "";
if (frontmatter.type !== "data_object") {
warnings.push(createWarning10(path2, "type", 'expected type "data_object"'));
}
if (!id) {
warnings.push(createWarning10(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning10(path2, "name", 'required frontmatter "name" is missing'));
}
const lines = normalizeLines(markdown);
const bodyStartLine = getBodyStartLine(lines);
const sectionRanges = getSectionRanges(lines, bodyStartLine);
const formatTable = parseSectionTable(lines, sectionRanges.Format, path2, "Format");
const recordsTable = parseSectionTable(lines, sectionRanges.Records, path2, "Records");
const fieldsTable = parseSectionTable(lines, sectionRanges.Fields, path2, "Fields");
warnings.push(...formatTable.warnings, ...recordsTable.warnings, ...fieldsTable.warnings);
const formatEntries = formatTable.rows.map((row) => ({
key: row.record.key?.trim() ?? "",
value: row.record.value?.trim() || void 0,
notes: row.record.notes?.trim() || void 0,
rowLine: row.line
}));
const records = recordsTable.rows.map((row) => ({
recordType: row.record.record_type?.trim() ?? "",
name: row.record.name?.trim() || void 0,
occurrence: row.record.occurrence?.trim() || void 0,
notes: row.record.notes?.trim() || void 0,
rowLine: row.line
}));
const fieldMode = detectFieldMode(fieldsTable.header);
if (fieldMode === "file_layout" && hasStandardAndFileLayoutColumns(fieldsTable.header)) {
warnings.push(
createSectionWarning2(
path2,
"Fields",
"Fields table mixes standard and file layout columns; parsed as file_layout"
)
);
}
const fields = fieldsTable.rows.map(
(row) => fieldMode === "file_layout" ? {
fieldMode,
recordType: row.record.record_type?.trim() || void 0,
no: row.record.no?.trim() || void 0,
name: row.record.name?.trim() ?? "",
label: row.record.label?.trim() || void 0,
type: row.record.type?.trim() || void 0,
length: row.record.length?.trim() || void 0,
required: row.record.required?.trim() || void 0,
position: row.record.position?.trim() || void 0,
fieldFormat: row.record.field_format?.trim() || void 0,
ref: row.record.ref?.trim() || void 0,
notes: row.record.notes?.trim() || void 0,
rowLine: row.line
} : {
fieldMode,
name: row.record.name?.trim() ?? "",
label: row.record.label?.trim() || void 0,
type: row.record.type?.trim() || void 0,
length: row.record.length?.trim() || void 0,
required: row.record.required?.trim() || void 0,
path: row.record.path?.trim() || void 0,
ref: row.record.ref?.trim() || void 0,
notes: row.record.notes?.trim() || void 0,
rowLine: row.line
}
);
const fallbackName = name || id || getFileStem6(path2) || "Untitled Data Object";
const sectionLines = Object.fromEntries(
Object.entries(sectionRanges).filter(([, range]) => range).map(([key, range]) => [key, range?.headingLine ?? bodyStartLine])
);
return {
file: {
fileType: "data-object",
schema: "data_object",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackName,
kind: kind || void 0,
dataFormat: dataFormat || void 0,
encoding: encoding || void 0,
delimiter: delimiter || void 0,
lineEnding: lineEnding || void 0,
hasHeader: hasHeader || void 0,
recordLength: recordLength || void 0,
summary: joinSectionLines4(sections.Summary),
notes: normalizeNotes2(sections.Notes),
formatEntries,
records,
fields,
fieldMode,
sectionLines
},
warnings
};
}
function normalizeLines(markdown) {
return markdown.replace(/\r\n/g, "\n").split("\n");
}
function getBodyStartLine(lines) {
if ((lines[0] ?? "").trim() !== "---") {
return 0;
}
for (let index = 1; index < lines.length; index += 1) {
if ((lines[index] ?? "").trim() === "---") {
return index + 1;
}
}
return 0;
}
function getSectionRanges(lines, bodyStartLine) {
const sectionNames = [
"Summary",
"Format",
"Records",
"Fields",
"Notes"
];
const boundarySectionNames = [...sectionNames, "Source Links"];
const ranges = {
Summary: null,
Format: null,
Records: null,
Fields: null,
Notes: null
};
const headings = [];
let inFence = false;
for (let index = bodyStartLine; index < lines.length; index += 1) {
const trimmed = (lines[index] ?? "").trim();
if (/^(```|~~~)/.test(trimmed)) {
inFence = !inFence;
continue;
}
if (inFence) {
continue;
}
const match = trimmed.match(/^##\s+(.+)$/);
if (!match) {
continue;
}
const name = match[1].trim();
if (boundarySectionNames.includes(name)) {
headings.push({ name, line: index });
}
}
for (let index = 0; index < headings.length; index += 1) {
const heading = headings[index];
const nextLine = headings[index + 1]?.line ?? lines.length;
if (!sectionNames.includes(heading.name)) {
continue;
}
ranges[heading.name] = {
headingLine: heading.line,
endLine: nextLine
};
}
return ranges;
}
function parseSectionTable(lines, range, path2, section) {
const warnings = [];
if (!range) {
return { header: [], rows: [], warnings };
}
let header = [];
const rows = [];
for (let index = range.headingLine + 1; index < range.endLine; index += 1) {
const line = lines[index] ?? "";
const trimmed = line.trim();
if (!trimmed.startsWith("|")) {
continue;
}
const cells = splitMarkdownTableRow(line);
if (!cells || cells.length === 0) {
continue;
}
if (isSeparatorRow(cells)) {
continue;
}
if (header.length === 0) {
header = cells.map((cell) => cell.trim());
continue;
}
const record = {};
for (let columnIndex = 0; columnIndex < header.length; columnIndex += 1) {
record[header[columnIndex]] = cells[columnIndex] ?? "";
}
if (Object.values(record).every((value) => !value.trim())) {
continue;
}
if (cells.length > header.length) {
warnings.push(
createSectionWarning2(
path2,
section,
`table row in section "${section}" has ${cells.length} columns, expected ${header.length}`
)
);
}
rows.push({ record, line: index });
}
if (section === "Format" && header.length > 0 && !matchesHeader(header, FORMAT_HEADERS)) {
warnings.push(
createSectionWarning2(path2, section, "Format table should use: key | value | notes")
);
}
if (section === "Records" && header.length > 0 && !matchesHeader(header, RECORD_HEADERS)) {
warnings.push(
createSectionWarning2(
path2,
section,
"Records table should use: record_type | name | occurrence | notes"
)
);
}
return { header, rows, warnings };
}
function detectFieldMode(header) {
return header.some((cell) => FILE_LAYOUT_HINTS.has(cell)) ? "file_layout" : "standard";
}
function hasStandardAndFileLayoutColumns(header) {
const hasFileLayout = header.some((cell) => FILE_LAYOUT_HINTS.has(cell));
const hasStandardOnly = header.some((cell) => ["path"].includes(cell));
return hasFileLayout && hasStandardOnly;
}
function matchesHeader(header, expected) {
return expected.every((value, index) => header[index] === value);
}
function isSeparatorRow(cells) {
return cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
}
function getFileStem6(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines4(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes2(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function createWarning10(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
function createSectionWarning2(path2, section, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field: section,
context: {
section
}
};
}
// src/parsers/domain-diagram-parser.ts
function parseDomainDiagramFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const title = typeof frontmatter.title === "string" ? frontmatter.title.trim() : "";
if (frontmatter.type !== "domain_diagram") {
warnings.push(createWarning11(path2, "type", 'expected type "domain_diagram"'));
}
if (!id) {
warnings.push(createWarning11(path2, "id", 'required frontmatter "id" is missing'));
}
const sourcesTable = parseDomainSourcesTable(sections["Domain Sources"], path2);
warnings.push(...sourcesTable.warnings);
const domainSources = sourcesTable.rows;
if (domainSources.length === 0) {
warnings.push({
code: "invalid-structure",
message: formatDomainDiagramNoValidSourcesMessage(),
severity: "warning",
path: path2,
field: "Domain Sources"
});
}
const fallbackTitle = name || title || id || getFileStem7(path2) || "Untitled Domain Diagram";
return {
file: {
fileType: "domain-diagram",
schema: "domain_diagram",
path: path2,
title: fallbackTitle,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: name || title || fallbackTitle,
domainSources
},
warnings
};
}
function createWarning11(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
function getFileStem7(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
// src/parsers/app-process-parser.ts
var INPUT_HEADERS = ["id", "data", "source", "required", "notes"];
var OUTPUT_HEADERS = ["id", "data", "target", "notes"];
var TRIGGER_HEADERS = ["id", "kind", "source", "event", "notes"];
var TRANSITION_HEADERS = ["id", "event", "to", "condition", "notes"];
var LEGACY_STEP_HEADERS = ["id", "lane", "label", "kind", "input", "output", "rule", "invoke", "screen", "notes"];
var DOMAIN_STEP_HEADERS = ["id", "domain", "label", "kind", "input", "output", "rule", "invoke", "screen", "notes"];
var TRANSITIONAL_STEP_HEADERS = ["id", "domain", "lane", "label", "kind", "input", "output", "rule", "invoke", "screen", "notes"];
var FLOW_HEADERS = ["from", "to", "condition", "label", "notes"];
function parseAppProcessFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const kind = typeof frontmatter.kind === "string" ? frontmatter.kind.trim() : "";
const flowDirection = normalizeAppProcessBusinessFlowDirection(frontmatter.flow_direction);
if (frontmatter.type !== "app_process") {
warnings.push(createWarning12(path2, "type", 'expected type "app_process"'));
}
if (!id) {
warnings.push(createWarning12(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning12(path2, "name", 'required frontmatter "name" is missing'));
}
const inputsTable = parseMarkdownTable(sections.Inputs, INPUT_HEADERS, path2, "Inputs");
const outputsTable = parseMarkdownTable(sections.Outputs, OUTPUT_HEADERS, path2, "Outputs");
const triggersTable = parseMarkdownTable(
sections.Triggers,
TRIGGER_HEADERS,
path2,
"Triggers"
);
const transitionsTable = parseMarkdownTable(
sections.Transitions,
TRANSITION_HEADERS,
path2,
"Transitions"
);
const domainsTable = parseDomainEntries(sections.Domains, path2);
const hasStructuredSteps = hasMarkdownTable(sections.Steps);
const stepsTable = hasStructuredSteps ? parseAppProcessStepsTable(sections.Steps, path2) : { rows: [], warnings: [] };
const flowsTable = hasMarkdownTable(sections.Flows) ? parseMarkdownTable(sections.Flows, FLOW_HEADERS, path2, "Flows") : { rows: [], warnings: [] };
const domainSourcesTable = "Domain Sources" in sections ? parseDomainSourcesTable(sections["Domain Sources"], path2) : { rows: [], warnings: [] };
const steps = stepsTable.rows.map((row) => ({
id: row.id?.trim() ?? "",
domain: row.domain?.trim() || void 0,
lane: row.lane?.trim() || void 0,
label: row.label?.trim() || void 0,
kind: row.kind?.trim() || void 0,
input: row.input?.trim() || void 0,
output: row.output?.trim() || void 0,
rule: row.rule?.trim() || void 0,
invoke: row.invoke?.trim() || void 0,
screen: row.screen?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow(Object.values(row)));
const flows = flowsTable.rows.map((row) => ({
from: row.from?.trim() ?? "",
to: row.to?.trim() ?? "",
condition: row.condition?.trim() || void 0,
label: row.label?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow(Object.values(row)));
warnings.push(
...inputsTable.warnings,
...outputsTable.warnings,
...triggersTable.warnings,
...transitionsTable.warnings,
...domainsTable.warnings,
...validateDomainEntries(path2, domainsTable.rows, {
skipUnknownParents: domainSourcesTable.rows.length > 0
}),
...stepsTable.warnings,
...flowsTable.warnings,
...domainSourcesTable.warnings
);
const fallbackName = name || id || getFileStem8(path2) || "Untitled App Process";
return {
file: {
fileType: "app-process",
schema: "app_process",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
domains: domainsTable.rows,
domainSources: domainSourcesTable.rows,
id,
name: fallbackName,
kind: kind || void 0,
flowDirection,
summary: joinSectionLines5(sections.Summary),
inputs: inputsTable.rows.map((row) => ({
id: row.id?.trim() ?? "",
data: row.data?.trim() || void 0,
source: row.source?.trim() || void 0,
required: row.required?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow(Object.values(row))),
outputs: outputsTable.rows.map((row) => ({
id: row.id?.trim() ?? "",
data: row.data?.trim() || void 0,
target: row.target?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow(Object.values(row))),
triggers: triggersTable.rows.map((row) => ({
id: row.id?.trim() ?? "",
kind: row.kind?.trim() || void 0,
source: row.source?.trim() || void 0,
event: row.event?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow(Object.values(row))),
transitions: transitionsTable.rows.map((row) => ({
id: row.id?.trim() ?? "",
event: row.event?.trim() || void 0,
to: row.to?.trim() || void 0,
condition: row.condition?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow(Object.values(row))),
steps,
flows,
hasExplicitFlows: hasStructuredSteps && flows.length > 0,
notes: normalizeNotes3(sections.Notes)
},
warnings
};
}
function getFileStem8(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines5(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes3(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function hasMarkdownTable(lines) {
const tableLines = (lines ?? []).map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (tableLines.length < 2) {
return false;
}
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(tableLines[1]);
}
function parseAppProcessStepsTable(lines, path2) {
if (!lines) {
return { rows: [], warnings: [] };
}
const normalizedLines = lines.map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (normalizedLines.length < 2) {
return {
rows: [],
warnings: normalizedLines.length === 0 ? [] : [createTableWarning3("invalid-table-row", path2, "Steps", 'table in section "Steps" is incomplete')]
};
}
const headers = splitMarkdownTableRow(normalizedLines[0]) ?? [];
const warnings = [];
if (!isSupportedStepHeaders(headers)) {
warnings.push(
createTableWarning3(
"invalid-table-column",
path2,
"Steps",
'table columns in section "Steps" do not match supported app_process step headers'
)
);
}
const rows = [];
normalizedLines.slice(2).forEach((rowLine, rowIndex) => {
const values = splitMarkdownTableRow(rowLine) ?? [];
if (isEmptyMarkdownTableDataRow(values)) {
return;
}
if (values.length !== headers.length) {
warnings.push(
createTableWarning3(
"invalid-table-row",
path2,
"Steps",
`table row in section "Steps" has ${values.length} columns, expected ${headers.length}`
)
);
return;
}
const row = {};
for (const [index, header] of headers.entries()) {
row[header] = values[index] ?? "";
}
const domain = row.domain?.trim() ?? "";
const lane = row.lane?.trim() ?? "";
if (domain && lane) {
warnings.push({
code: "invalid-structure",
message: `Step "${row.id?.trim() || row.label?.trim() || rowIndex + 1}" has both domain and lane. domain is used and lane is ignored.`,
severity: "warning",
path: path2,
field: "Steps.domain",
context: { rowIndex: rowIndex + 1 }
});
}
rows.push(row);
});
return { rows, warnings };
}
function isSupportedStepHeaders(headers) {
return sameHeaders5(headers, LEGACY_STEP_HEADERS) || sameHeaders5(headers, DOMAIN_STEP_HEADERS) || sameHeaders5(headers, TRANSITIONAL_STEP_HEADERS);
}
function sameHeaders5(actual, expected) {
return actual.length === expected.length && actual.every((header, index) => header === expected[index]);
}
function isEmptyRow(values) {
return values.every((value) => !value?.trim());
}
function createTableWarning3(code, path2, field, message) {
return {
code,
message,
severity: "warning",
path: path2,
field
};
}
function createWarning12(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/screen-parser.ts
var LAYOUT_HEADERS = ["id", "label", "kind", "purpose", "notes"];
var FIELD_HEADERS = [
"id",
"label",
"kind",
"layout",
"data_type",
"required",
"ref",
"rule",
"notes"
];
var FIELD_HEADERS_WITH_CONDITION = [
"id",
"label",
"kind",
"layout",
"data_type",
"required",
"ref",
"condition",
"rule",
"notes"
];
var FIELD_HEADERS_WITH_RULE_BEFORE_CONDITION = [
"id",
"label",
"kind",
"layout",
"data_type",
"required",
"ref",
"rule",
"condition",
"notes"
];
var LEGACY_FIELD_HEADERS = [
"id",
"label",
"kind",
"data_type",
"required",
"ref",
"rule",
"notes"
];
var LEGACY_FIELD_HEADERS_WITH_CONDITION = [
"id",
"label",
"kind",
"data_type",
"required",
"ref",
"condition",
"rule",
"notes"
];
var LEGACY_FIELD_HEADERS_WITH_RULE_BEFORE_CONDITION = [
"id",
"label",
"kind",
"data_type",
"required",
"ref",
"rule",
"condition",
"notes"
];
var ACTION_HEADERS = [
"id",
"label",
"kind",
"target",
"event",
"invoke",
"transition",
"rule",
"notes"
];
var ACTION_HEADERS_WITH_CONDITION = [
"id",
"label",
"kind",
"target",
"event",
"condition",
"invoke",
"transition",
"rule",
"notes"
];
var ACTION_HEADERS_WITH_CONDITION_AFTER_RULE = [
"id",
"label",
"kind",
"target",
"event",
"invoke",
"transition",
"rule",
"condition",
"notes"
];
var MESSAGE_HEADERS = ["id", "text", "severity", "timing", "notes"];
var MESSAGE_HEADERS_WITH_CONDITION = [
"id",
"text",
"severity",
"timing",
"condition",
"notes"
];
var LEGACY_MESSAGE_HEADERS = ["ref", "timing", "notes"];
var LEGACY_TRANSITION_HEADERS = ["id", "event", "to", "condition", "notes"];
var LOCAL_PROCESS_STEP_HEADERS = [
"id",
"label",
"kind",
"condition",
"input",
"output",
"rule",
"invoke",
"screen",
"notes"
];
var LOCAL_PROCESS_ERROR_HEADERS = [
"id",
"condition",
"message",
"notes"
];
function parseScreenFile(markdown, path2) {
const normalizedMarkdown = markdown.replace(/\r\n/g, "\n");
const frontmatterResult = parseFrontmatter(normalizedMarkdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const body = frontmatterResult.file.body;
const sections = extractMarkdownSections(body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const screenType = typeof frontmatter.screen_type === "string" ? frontmatter.screen_type.trim() : "";
if (frontmatter.type !== "screen") {
warnings.push(createWarning13(path2, "type", 'expected type "screen"'));
}
if (!id) {
warnings.push(createWarning13(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning13(path2, "name", 'required frontmatter "name" is missing'));
}
const bodyLines = body.split("\n");
const bodyStartLine = getBodyStartLine2(normalizedMarkdown);
const sectionLines = getSectionLineNumbers(bodyLines, bodyStartLine);
const layoutTable = readSectionTable(bodyLines, bodyStartLine, "Layout");
const fieldsTable = readSectionTable(bodyLines, bodyStartLine, "Fields");
const actionsTable = readSectionTable(bodyLines, bodyStartLine, "Actions");
const messagesTable = readSectionTable(bodyLines, bodyStartLine, "Messages");
const transitionsTable = readSectionTable(bodyLines, bodyStartLine, "Transitions");
const localProcesses = collectLocalProcesses(bodyLines, bodyStartLine);
const layoutHeaders = layoutTable.headers;
if (layoutHeaders.length > 0 && !sameHeaders6(layoutHeaders, LAYOUT_HEADERS)) {
warnings.push(createWarning13(path2, "Layout", 'table columns in section "Layout" do not match expected headers'));
}
const fieldHeaders = fieldsTable.headers;
const isCanonicalFields = sameHeaders6(fieldHeaders, FIELD_HEADERS);
const isCanonicalFieldsWithCondition = sameHeaders6(fieldHeaders, FIELD_HEADERS_WITH_CONDITION);
const isCanonicalFieldsWithRuleBeforeCondition = sameHeaders6(fieldHeaders, FIELD_HEADERS_WITH_RULE_BEFORE_CONDITION);
const isLegacyFields = sameHeaders6(fieldHeaders, LEGACY_FIELD_HEADERS);
const isLegacyFieldsWithCondition = sameHeaders6(fieldHeaders, LEGACY_FIELD_HEADERS_WITH_CONDITION);
const isLegacyFieldsWithRuleBeforeCondition = sameHeaders6(fieldHeaders, LEGACY_FIELD_HEADERS_WITH_RULE_BEFORE_CONDITION);
if (fieldHeaders.length > 0 && !isCanonicalFields && !isCanonicalFieldsWithCondition && !isCanonicalFieldsWithRuleBeforeCondition && !isLegacyFields && !isLegacyFieldsWithCondition && !isLegacyFieldsWithRuleBeforeCondition) {
warnings.push(createWarning13(path2, "Fields", 'table columns in section "Fields" do not match expected screen field headers'));
}
const actionHeaders = actionsTable.headers;
if (actionHeaders.length > 0 && !sameHeaders6(actionHeaders, ACTION_HEADERS) && !sameHeaders6(actionHeaders, ACTION_HEADERS_WITH_CONDITION) && !sameHeaders6(actionHeaders, ACTION_HEADERS_WITH_CONDITION_AFTER_RULE)) {
warnings.push(createWarning13(path2, "Actions", 'table columns in section "Actions" do not match expected headers'));
}
const messageHeaders = messagesTable.headers;
const isCanonicalMessages = sameHeaders6(messageHeaders, MESSAGE_HEADERS);
const isCanonicalMessagesWithCondition = sameHeaders6(messageHeaders, MESSAGE_HEADERS_WITH_CONDITION);
const isLegacyMessages = sameHeaders6(messageHeaders, LEGACY_MESSAGE_HEADERS);
if (messageHeaders.length > 0 && !isCanonicalMessages && !isCanonicalMessagesWithCondition && !isLegacyMessages) {
warnings.push(createWarning13(path2, "Messages", 'table columns in section "Messages" do not match expected headers'));
}
const transitionHeaders = transitionsTable.headers;
if (transitionHeaders.length > 0 && !sameHeaders6(transitionHeaders, LEGACY_TRANSITION_HEADERS)) {
warnings.push(createWarning13(path2, "Transitions", 'table columns in section "Transitions" do not match expected legacy headers'));
}
const fallbackName = name || id || getFileStem9(path2) || "Untitled Screen";
return {
file: {
fileType: "screen",
schema: "screen",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackName,
screenType: screenType || void 0,
summary: joinSectionLines6(sections.Summary),
layouts: layoutTable.rows.map((row) => ({
id: row.record.id?.trim() ?? "",
label: row.record.label?.trim() || void 0,
kind: row.record.kind?.trim() || void 0,
purpose: row.record.purpose?.trim() || void 0,
notes: row.record.notes?.trim() || void 0,
rowLine: row.rowLine
})).filter((row) => !isEmptyRow2(Object.values(row))),
fields: fieldsTable.rows.map((row) => {
const record = row.record;
return {
id: record.id?.trim() ?? "",
label: record.label?.trim() || void 0,
kind: record.kind?.trim() || void 0,
layout: record.layout?.trim() || void 0,
dataType: record.data_type?.trim() || void 0,
required: record.required?.trim() || void 0,
ref: record.ref?.trim() || void 0,
rule: record.rule?.trim() || void 0,
condition: record.condition?.trim() || void 0,
notes: record.notes?.trim() || void 0,
rowLine: row.rowLine
};
}).filter((row) => !isEmptyRow2(Object.values(row))),
actions: actionsTable.rows.map((row) => mapActionRow(row.record, row.rowLine)).filter((row) => !isEmptyRow2(Object.values(row))),
messages: messagesTable.rows.map((row) => mapMessageRow(row.record, row.rowLine, isLegacyMessages)).filter((row) => !isEmptyRow2(Object.values(row))),
localProcesses,
legacyTransitions: transitionsTable.rows.map((row) => mapLegacyTransitionRow(row.record, row.rowLine)).filter((row) => !isEmptyRow2(Object.values(row))),
notes: normalizeNotes4(sections.Notes),
sectionLines
},
warnings
};
}
function getBodyStartLine2(markdown) {
if (!markdown.startsWith("---\n")) {
return 0;
}
const lines = markdown.split("\n");
for (let index = 1; index < lines.length; index += 1) {
if (lines[index].trim() === "---") {
return index + 1;
}
}
return 0;
}
function getSectionLineNumbers(bodyLines, bodyStartLine) {
const lines = {};
for (let index = 0; index < bodyLines.length; index += 1) {
const trimmed = bodyLines[index].trim();
if (trimmed === "# Summary") {
lines.Summary = bodyStartLine + index;
continue;
}
const match = trimmed.match(/^##\s+(.+)$/);
if (!match) {
continue;
}
const sectionName = match[1].trim();
lines[sectionName] = bodyStartLine + index;
}
return lines;
}
function readSectionTable(bodyLines, bodyStartLine, sectionName) {
const sectionBody = getSectionBodyLines(bodyLines, sectionName);
const tableLines = sectionBody.map((entry) => ({ ...entry, trimmed: entry.text.trim() })).filter((entry) => entry.trimmed.startsWith("|"));
if (tableLines.length < 2) {
return { headers: [], rows: [] };
}
const headers = splitMarkdownTableRow(tableLines[0].trimmed) ?? [];
if (headers.length === 0) {
return { headers: [], rows: [] };
}
const rows = [];
for (const rowLine of tableLines.slice(2)) {
const values = splitMarkdownTableRow(rowLine.trimmed) ?? [];
if (values.length !== headers.length) {
continue;
}
const record = {};
for (const [index, header] of headers.entries()) {
record[header] = values[index] ?? "";
}
if (Object.values(record).every((value) => !value.trim())) {
continue;
}
rows.push({
record,
rowLine: bodyStartLine + rowLine.index
});
}
return { headers, rows };
}
function getSectionBodyLines(bodyLines, sectionName) {
const entries = [];
let inSection = false;
for (let index = 0; index < bodyLines.length; index += 1) {
const line = bodyLines[index];
const trimmed = line.trim();
if (sectionName === "Summary" && trimmed === "# Summary") {
inSection = true;
continue;
}
const topLevelHeading = trimmed.match(/^##\s+(.+)$/);
if (topLevelHeading) {
const current = topLevelHeading[1].trim();
if (inSection && current !== sectionName) {
break;
}
inSection = current === sectionName;
continue;
}
if (inSection) {
entries.push({ index, text: line });
}
}
return entries;
}
function collectLocalProcesses(bodyLines, bodyStartLine) {
const localProcessLines = getSectionBodyLines(bodyLines, "Local Processes");
const processes = [];
for (let index = 0; index < localProcessLines.length; index += 1) {
const entry = localProcessLines[index];
const headingMatch = entry.text.trim().match(/^###\s+(.+)$/);
if (!headingMatch) {
continue;
}
const heading = headingMatch[1].trim();
let summary;
const processLines = getLocalProcessBodyLines(localProcessLines, index + 1);
const stepsTable = readLocalProcessSubsectionTable(
processLines,
bodyStartLine,
"Steps",
LOCAL_PROCESS_STEP_HEADERS
);
const errorsTable = readLocalProcessSubsectionTable(
processLines,
bodyStartLine,
"Errors",
LOCAL_PROCESS_ERROR_HEADERS
);
for (let nextIndex = 0; nextIndex < processLines.length; nextIndex += 1) {
const nextLine = processLines[nextIndex].text.trim();
if (/^####\s+Summary$/.test(nextLine)) {
const collected = [];
for (let bodyIndex = nextIndex + 1; bodyIndex < processLines.length; bodyIndex += 1) {
const bodyLine = processLines[bodyIndex].text.trim();
if (/^####\s+/.test(bodyLine)) {
break;
}
if (bodyLine) {
collected.push(bodyLine);
}
}
summary = collected.join(" ").trim() || void 0;
break;
}
}
processes.push({
id: heading,
heading,
summary,
steps: stepsTable.rows.map((row) => mapLocalProcessStepRow(row.record, row.rowLine)).filter((row) => !isEmptyRow2(Object.values(row))),
errors: errorsTable.rows.map((row) => mapLocalProcessErrorRow(row.record, row.rowLine)).filter((row) => !isEmptyRow2(Object.values(row))),
line: bodyStartLine + entry.index
});
}
return processes;
}
function getLocalProcessBodyLines(localProcessLines, startIndex) {
const entries = [];
for (let index = startIndex; index < localProcessLines.length; index += 1) {
const entry = localProcessLines[index];
if (/^###\s+/.test(entry.text.trim())) {
break;
}
entries.push(entry);
}
return entries;
}
function readLocalProcessSubsectionTable(processLines, bodyStartLine, subsectionName, expectedHeaders) {
const subsectionLines = [];
let inSubsection = false;
for (const entry of processLines) {
const trimmed = entry.text.trim();
const headingMatch = trimmed.match(/^####\s+(.+)$/);
if (headingMatch) {
if (inSubsection) {
break;
}
inSubsection = headingMatch[1].trim() === subsectionName;
continue;
}
if (inSubsection) {
subsectionLines.push(entry);
}
}
const table = readTableFromEntries(subsectionLines, bodyStartLine);
if (table.headers.length > 0 && !sameHeaders6(table.headers, expectedHeaders)) {
return { headers: table.headers, rows: [] };
}
return table;
}
function readTableFromEntries(entries, bodyStartLine) {
const tableLines = entries.map((entry) => ({ ...entry, trimmed: entry.text.trim() })).filter((entry) => entry.trimmed.startsWith("|"));
if (tableLines.length < 2) {
return { headers: [], rows: [] };
}
const headers = splitMarkdownTableRow(tableLines[0].trimmed) ?? [];
if (headers.length === 0) {
return { headers: [], rows: [] };
}
const rows = [];
for (const rowLine of tableLines.slice(2)) {
const values = splitMarkdownTableRow(rowLine.trimmed) ?? [];
if (values.length !== headers.length) {
continue;
}
const record = {};
for (const [index, header] of headers.entries()) {
record[header] = values[index] ?? "";
}
if (Object.values(record).every((value) => !value.trim())) {
continue;
}
rows.push({
record,
rowLine: bodyStartLine + rowLine.index
});
}
return { headers, rows };
}
function mapActionRow(record, rowLine) {
return {
id: record.id?.trim() || void 0,
label: record.label?.trim() || void 0,
kind: record.kind?.trim() || void 0,
target: record.target?.trim() || void 0,
event: record.event?.trim() || void 0,
invoke: record.invoke?.trim() || void 0,
transition: record.transition?.trim() || void 0,
rule: record.rule?.trim() || void 0,
condition: record.condition?.trim() || void 0,
notes: record.notes?.trim() || void 0,
rowLine
};
}
function mapMessageRow(record, rowLine, isLegacyMessages) {
if (isLegacyMessages) {
return {
id: void 0,
text: record.ref?.trim() || void 0,
severity: void 0,
timing: record.timing?.trim() || void 0,
notes: record.notes?.trim() || void 0,
rowLine
};
}
return {
id: record.id?.trim() || void 0,
text: record.text?.trim() || void 0,
severity: record.severity?.trim() || void 0,
timing: record.timing?.trim() || void 0,
condition: record.condition?.trim() || void 0,
notes: record.notes?.trim() || void 0,
rowLine
};
}
function mapLocalProcessStepRow(record, rowLine) {
return {
id: record.id?.trim() || void 0,
label: record.label?.trim() || void 0,
kind: record.kind?.trim() || void 0,
condition: record.condition?.trim() || void 0,
input: record.input?.trim() || void 0,
output: record.output?.trim() || void 0,
rule: record.rule?.trim() || void 0,
invoke: record.invoke?.trim() || void 0,
screen: record.screen?.trim() || void 0,
notes: record.notes?.trim() || void 0,
rowLine
};
}
function mapLocalProcessErrorRow(record, rowLine) {
return {
id: record.id?.trim() || void 0,
condition: record.condition?.trim() || void 0,
message: record.message?.trim() || void 0,
notes: record.notes?.trim() || void 0,
rowLine
};
}
function mapLegacyTransitionRow(record, rowLine) {
return {
id: record.id?.trim() || void 0,
event: record.event?.trim() || void 0,
to: record.to?.trim() || void 0,
condition: record.condition?.trim() || void 0,
notes: record.notes?.trim() || void 0,
rowLine
};
}
function sameHeaders6(actual, expected) {
if (actual.length !== expected.length) {
return false;
}
return actual.every((header, index) => header === expected[index]);
}
function getFileStem9(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines6(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes4(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function isEmptyRow2(values) {
return values.every((value) => {
if (value === void 0 || value === null) {
return true;
}
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return !String(value).trim();
}
return false;
});
}
function createWarning13(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/codeset-parser.ts
var VALUE_HEADERS = ["code", "label", "sort_order", "active", "notes"];
function parseCodeSetFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const kind = typeof frontmatter.kind === "string" ? frontmatter.kind.trim() : "";
if (frontmatter.type !== "codeset") {
warnings.push(createWarning14(path2, "type", 'expected type "codeset"'));
}
if (!id) {
warnings.push(createWarning14(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning14(path2, "name", 'required frontmatter "name" is missing'));
}
const valuesTable = parseMarkdownTable(sections.Values, VALUE_HEADERS, path2, "Values");
warnings.push(...valuesTable.warnings);
const fallbackName = name || id || getFileStem10(path2) || "Untitled CodeSet";
return {
file: {
fileType: "codeset",
schema: "codeset",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackName,
kind: kind || void 0,
summary: joinSectionLines7(sections.Summary),
values: valuesTable.rows.map((row) => ({
code: row.code?.trim() ?? "",
label: row.label?.trim() || void 0,
sortOrder: row.sort_order?.trim() || void 0,
active: row.active?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow3(Object.values(row))),
notes: normalizeNotes5(sections.Notes)
},
warnings
};
}
function getFileStem10(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines7(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes5(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function isEmptyRow3(values) {
return values.every((value) => !value?.trim());
}
function createWarning14(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/message-parser.ts
var MESSAGE_HEADERS2 = [
"message_id",
"text",
"severity",
"timing",
"audience",
"active",
"notes"
];
function parseMessageFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const kind = typeof frontmatter.kind === "string" ? frontmatter.kind.trim() : "";
if (frontmatter.type !== "message") {
warnings.push(createWarning15(path2, "type", 'expected type "message"'));
}
if (!id) {
warnings.push(createWarning15(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning15(path2, "name", 'required frontmatter "name" is missing'));
}
const messagesTable = parseMarkdownTable(sections.Messages, MESSAGE_HEADERS2, path2, "Messages");
warnings.push(...messagesTable.warnings);
const fallbackName = name || id || getFileStem11(path2) || "Untitled Message Set";
return {
file: {
fileType: "message",
schema: "message",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackName,
kind: kind || void 0,
summary: joinSectionLines8(sections.Summary),
messages: messagesTable.rows.map((row) => ({
messageId: row.message_id?.trim() ?? "",
text: row.text?.trim() || void 0,
severity: row.severity?.trim() || void 0,
timing: row.timing?.trim() || void 0,
audience: row.audience?.trim() || void 0,
active: row.active?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow4(Object.values(row))),
notes: normalizeNotes6(sections.Notes)
},
warnings
};
}
function getFileStem11(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines8(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes6(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function isEmptyRow4(values) {
return values.every((value) => !value?.trim());
}
function createWarning15(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/rule-parser.ts
var INPUT_HEADERS2 = ["id", "data", "source", "required", "notes"];
var REFERENCE_HEADERS = ["ref", "usage", "notes"];
var MESSAGE_HEADERS3 = ["condition", "message", "severity", "notes"];
function parseRuleFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const kind = typeof frontmatter.kind === "string" ? frontmatter.kind.trim() : "";
if (frontmatter.type !== "rule") {
warnings.push(createWarning16(path2, "type", 'expected type "rule"'));
}
if (!id) {
warnings.push(createWarning16(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning16(path2, "name", 'required frontmatter "name" is missing'));
}
const inputsTable = parseMarkdownTable(sections.Inputs, INPUT_HEADERS2, path2, "Inputs");
const referencesTable = parseMarkdownTable(
sections.References,
REFERENCE_HEADERS,
path2,
"References"
);
const messagesTable = parseMarkdownTable(sections.Messages, MESSAGE_HEADERS3, path2, "Messages");
warnings.push(...inputsTable.warnings, ...referencesTable.warnings, ...messagesTable.warnings);
const fallbackName = name || id || getFileStem12(path2) || "Untitled Rule";
return {
file: {
fileType: "rule",
schema: "rule",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackName,
kind: kind || void 0,
summary: joinSectionLines9(sections.Summary),
inputs: inputsTable.rows.map((row) => ({
id: row.id?.trim() ?? "",
data: row.data?.trim() || void 0,
source: row.source?.trim() || void 0,
required: row.required?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow5(Object.values(row))),
references: referencesTable.rows.map((row) => ({
ref: row.ref?.trim() || void 0,
usage: row.usage?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow5(Object.values(row))),
messages: messagesTable.rows.map((row) => ({
condition: row.condition?.trim() || void 0,
message: row.message?.trim() || void 0,
severity: row.severity?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow5(Object.values(row))),
notes: normalizeNotes7(sections.Notes)
},
warnings
};
}
function getFileStem12(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines9(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes7(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function isEmptyRow5(values) {
return values.every((value) => !value?.trim());
}
function createWarning16(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/mapping-parser.ts
var SCOPE_HEADERS = ["role", "ref", "notes"];
var MAPPING_HEADERS = ["target_ref", "source_ref", "transform", "rule", "required", "notes"];
var LEGACY_MAPPING_HEADERS = ["source_ref", "target_ref", "transform", "rule", "required", "notes"];
function parseMappingFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const kind = typeof frontmatter.kind === "string" ? frontmatter.kind.trim() : "";
const source = typeof frontmatter.source === "string" ? frontmatter.source.trim() : "";
const target = typeof frontmatter.target === "string" ? frontmatter.target.trim() : "";
if (frontmatter.type !== "mapping") {
warnings.push(createWarning17(path2, "type", 'expected type "mapping"'));
}
if (!id) {
warnings.push(createWarning17(path2, "id", 'required frontmatter "id" is missing'));
}
if (!name) {
warnings.push(createWarning17(path2, "name", 'required frontmatter "name" is missing'));
}
const scopeTable = parseMarkdownTable(sections.Scope, SCOPE_HEADERS, path2, "Scope");
const mappingsTable = parseMarkdownTable(
sections.Mappings,
getAcceptedMappingHeaders(sections.Mappings),
path2,
"Mappings"
);
warnings.push(...scopeTable.warnings, ...mappingsTable.warnings);
const fallbackName = name || id || getFileStem13(path2) || "Untitled Mapping";
return {
file: {
fileType: "mapping",
schema: "mapping",
path: path2,
title: fallbackName,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: fallbackName,
kind: kind || void 0,
source: source || void 0,
target: target || void 0,
summary: joinSectionLines10(sections.Summary),
scope: scopeTable.rows.map((row) => ({
role: row.role?.trim() || void 0,
ref: row.ref?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow6(Object.values(row))),
mappings: mappingsTable.rows.map((row) => ({
sourceRef: row.source_ref?.trim() || void 0,
targetRef: row.target_ref?.trim() || void 0,
transform: row.transform?.trim() || void 0,
rule: row.rule?.trim() || void 0,
required: row.required?.trim() || void 0,
notes: row.notes?.trim() || void 0
})).filter((row) => !isEmptyRow6(Object.values(row))),
notes: normalizeNotes8(sections.Notes)
},
warnings
};
}
function getAcceptedMappingHeaders(lines) {
const actualHeader = getMarkdownTableHeader(lines);
if (actualHeader && sameHeaders7(actualHeader, LEGACY_MAPPING_HEADERS)) {
return [...LEGACY_MAPPING_HEADERS];
}
return [...MAPPING_HEADERS];
}
function getMarkdownTableHeader(lines) {
const headerLine = lines?.map((line) => line.trim()).find((line) => line.startsWith("|"));
return headerLine ? splitMarkdownTableRow(headerLine) : null;
}
function sameHeaders7(headers, expectedHeaders) {
return headers.length === expectedHeaders.length && expectedHeaders.every((header, index) => headers[index] === header);
}
function getFileStem13(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
function joinSectionLines10(lines) {
const value = (lines ?? []).join("\n").trim();
return value || void 0;
}
function normalizeNotes8(lines) {
const notes = (lines ?? []).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^-\s+/, ""));
return notes.length > 0 ? notes : void 0;
}
function isEmptyRow6(values) {
return values.every((value) => !value?.trim());
}
function createWarning17(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
// src/parsers/color-scheme-parser.ts
var COLOR_HEADERS = ["target", "kind", "fill", "stroke", "text", "notes"];
var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
function parseColorSchemeFile(markdown, path2) {
const frontmatterResult = parseFrontmatter(markdown);
const frontmatter = frontmatterResult.file.frontmatter ?? {};
const sections = extractMarkdownSections(frontmatterResult.file.body);
const warnings = frontmatterResult.warnings.map((warning) => ({
...warning,
path: warning.path ?? path2
}));
const id = typeof frontmatter.id === "string" ? frontmatter.id.trim() : "";
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
const title = typeof frontmatter.title === "string" ? frontmatter.title.trim() : "";
if (frontmatter.type !== "color_scheme") {
warnings.push(createWarning18(path2, "type", 'expected type "color_scheme"'));
}
if (!id) {
warnings.push(createWarning18(path2, "id", 'required frontmatter "id" is missing'));
}
const colorTable = parseMarkdownTable(sections.Colors, COLOR_HEADERS, path2, "Colors");
warnings.push(...colorTable.warnings);
const colors = [];
const hasInvalidColorHeader = colorTable.warnings.some(
(warning) => warning.code === "invalid-table-column" && warning.field === "Colors"
);
const seenKeys = /* @__PURE__ */ new Set();
if (!hasInvalidColorHeader) {
colorTable.rows.forEach((row, rowIndex) => {
const target = row.target?.trim() ?? "";
const kind = row.kind?.trim() ?? "";
const fill = row.fill?.trim() ?? "";
const stroke = row.stroke?.trim() ?? "";
const text = row.text?.trim() ?? "";
const notes = row.notes?.trim() ?? "";
if (!kind) {
warnings.push({
code: "invalid-structure",
message: formatColorSchemeKindRequiredMessage(),
severity: "error",
path: path2,
field: "Colors.kind",
context: { rowIndex: rowIndex + 1 }
});
return;
}
for (const [field, value] of [
["fill", fill],
["stroke", stroke],
["text", text]
]) {
if (value && !HEX_COLOR_PATTERN.test(value)) {
warnings.push({
code: "invalid-structure",
message: formatColorSchemeInvalidColorMessage(field, value),
severity: "warning",
path: path2,
field: `Colors.${field}`,
context: { rowIndex: rowIndex + 1 }
});
}
}
const key = `${target.toLowerCase()}::${kind.toLowerCase()}`;
if (seenKeys.has(key)) {
warnings.push({
code: "invalid-structure",
message: formatColorSchemeDuplicateEntryMessage(target, kind),
severity: "warning",
path: path2,
field: "Colors.kind",
context: { rowIndex: rowIndex + 1 }
});
}
seenKeys.add(key);
colors.push({
target: target || void 0,
kind,
fill: fill || void 0,
stroke: stroke || void 0,
text: text || void 0,
notes: notes || void 0,
rowIndex
});
});
}
const fallbackTitle = name || title || id || getFileStem14(path2) || "Untitled Color Scheme";
return {
file: {
fileType: "color-scheme",
schema: "color_scheme",
path: path2,
title: fallbackTitle,
frontmatter,
sections,
sourceLinks: parseSourceLinks(sections["Source Links"]),
id,
name: name || title || fallbackTitle,
colors
},
warnings
};
}
function createWarning18(path2, field, message) {
return {
code: "invalid-structure",
message,
severity: "warning",
path: path2,
field
};
}
function getFileStem14(path2) {
return path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "") ?? "";
}
// src/core/validator.ts
var RESERVED_OBJECT_KINDS2 = /* @__PURE__ */ new Set(["actor", "usecase"]);
var RESERVED_RELATION_KINDS2 = /* @__PURE__ */ new Set([
"include",
"extend",
"transition",
"message"
]);
var RESERVED_DIAGRAM_KINDS = /* @__PURE__ */ new Set(["usecase", "activity", "sequence"]);
var STANDALONE_DOMAIN_CANONICAL_FIELDS = [
"name",
"kind",
"parent"
];
function validateVaultIndex(index) {
const warnings = [];
const idRegistry = /* @__PURE__ */ new Map();
for (const [objectId, object] of Object.entries(index.objectsById)) {
registerId(idRegistry, objectId, object.path, warnings);
validateFilenameMatchesId(objectId, object.path, warnings);
validateReservedObjectKind(object, objectId, warnings);
}
for (const [entityId, entity] of Object.entries(index.erEntitiesById)) {
registerId(idRegistry, entityId, entity.path, warnings);
validateFilenameMatchesId(entityId, entity.path, warnings);
}
validateErRelationIds(index, warnings);
for (const [dfdObjectId, dfdObject] of Object.entries(index.dfdObjectsById)) {
registerId(idRegistry, dfdObjectId, dfdObject.path, warnings);
validateFilenameMatchesId(dfdObjectId, dfdObject.path, warnings);
}
for (const [dataObjectId, dataObject] of Object.entries(index.dataObjectsById)) {
registerId(idRegistry, dataObjectId, dataObject.path, warnings);
validateFilenameMatchesId(dataObjectId, dataObject.path, warnings);
validateDataObject(dataObject, index, warnings);
}
for (const [fileId, relationsFile] of Object.entries(index.relationsFilesById)) {
registerId(idRegistry, fileId, relationsFile.path, warnings);
validateFilenameMatchesId(fileId, relationsFile.path, warnings);
for (const relation of relationsFile.relations) {
if (relation.id) {
registerId(idRegistry, relation.id, relationsFile.path, warnings);
}
validateRelationEndpoints(relation.source, relation.target, relationsFile.path, index, warnings);
if (RESERVED_RELATION_KINDS2.has(relation.kind)) {
warnings.push({
code: "reserved-relation-kind-used",
message: `reserved kind used: "${relation.kind}"`,
severity: "info",
path: relationsFile.path,
field: "kind"
});
}
}
}
validateStandaloneDomains(index, warnings);
for (const [diagramId, diagram] of Object.entries(index.diagramsById)) {
registerId(idRegistry, diagramId, diagram.path, warnings);
validateFilenameMatchesId(diagramId, diagram.path, warnings);
validateDiagram(diagram, index, warnings);
}
return dedupeWarnings(warnings);
}
function validateStandaloneDomains(index, warnings) {
const entriesByDomainId = /* @__PURE__ */ new Map();
for (const model of Object.values(index.modelsByFilePath)) {
if (model.fileType !== "domains") {
continue;
}
for (const domain of model.domains) {
if (!entriesByDomainId.has(domain.id)) {
entriesByDomainId.set(domain.id, []);
}
entriesByDomainId.get(domain.id).push({
domain,
path: model.path
});
}
}
validateStandaloneDomainParents(entriesByDomainId, warnings);
for (const entries of entriesByDomainId.values()) {
if (entries.length < 2) {
continue;
}
const sortedEntries = [...entries].sort((left, right) => {
const pathOrder = left.path.localeCompare(right.path);
if (pathOrder !== 0) {
return pathOrder;
}
return left.domain.rowIndex - right.domain.rowIndex;
});
const canonical = sortedEntries[0];
if (!canonical) {
continue;
}
for (const entry of sortedEntries) {
warnings.push({
code: "invalid-structure",
message: formatStandaloneDomainDuplicateMessage(entry.domain.id),
severity: "warning",
path: entry.path,
field: "Domains.id",
context: { rowIndex: entry.domain.rowIndex + 1 }
});
}
compareStandaloneDomainFields(sortedEntries, warnings);
}
}
function validateStandaloneDomainParents(entriesByDomainId, warnings) {
for (const entries of entriesByDomainId.values()) {
for (const entry of entries) {
const parent = entry.domain.parent?.trim();
if (!parent || parent === entry.domain.id || entriesByDomainId.has(parent)) {
continue;
}
warnings.push({
code: "unresolved-reference",
message: formatDomainParentUnknownMessage(parent),
severity: "warning",
path: entry.path,
field: "Domains.parent",
context: { rowIndex: entry.domain.rowIndex + 1 }
});
}
}
}
function compareStandaloneDomainFields(entries, warnings) {
for (const field of STANDALONE_DOMAIN_CANONICAL_FIELDS) {
const values = new Set(entries.map((entry) => entry.domain[field]?.trim() ?? ""));
if (values.size < 2) {
continue;
}
for (const entry of entries) {
warnings.push({
code: "invalid-structure",
message: formatStandaloneDomainFieldConflictMessage(
entry.domain.id,
field
),
severity: "warning",
path: entry.path,
field: `Domains.${field}`,
context: { rowIndex: entry.domain.rowIndex + 1 }
});
}
}
}
function validateDiagram(diagram, index, warnings) {
if (RESERVED_DIAGRAM_KINDS.has(diagram.kind)) {
warnings.push({
code: "reserved-diagram-kind-used",
message: `reserved kind used: "${diagram.kind}"`,
severity: "info",
path: diagram.path,
field: "diagram_kind"
});
}
if (diagram.schema === "dfd_diagram") {
const dfdDiagram = diagram;
validateDfdLocalDomains(dfdDiagram, index, warnings);
validateDfdObjectDomains(dfdDiagram, index, warnings);
const objectEntries = dfdDiagram.objectEntries.length > 0 ? dfdDiagram.objectEntries : dfdDiagram.objectRefs.map((objectRef, rowIndex) => ({
ref: objectRef,
rowIndex,
compatibilityMode: "legacy_ref_only"
}));
const objectIdentityKeys = /* @__PURE__ */ new Set();
const objectIds = /* @__PURE__ */ new Set();
for (const entry of objectEntries) {
if (entry.id?.trim()) {
objectIds.add(entry.id.trim());
}
const ref = entry.ref?.trim();
if (!ref) {
continue;
}
const identity = resolveReferenceIdentity(ref, index);
if (!identity.resolvedModel) {
warnings.push({
code: "unresolved-reference",
message: `unresolved object ref "${ref}"`,
severity: "warning",
path: diagram.path,
field: "Objects"
});
continue;
}
for (const key of buildReferenceIdentityKeys(identity)) {
objectIdentityKeys.add(key);
}
}
for (const edge of dfdDiagram.edges) {
if (edge.source && objectIds.has(edge.source)) {
} else {
const sourceIdentity = edge.source ? resolveReferenceIdentity(edge.source, index) : null;
const sourceResolved = !!edge.source && Boolean(sourceIdentity?.resolvedModel);
const sourceIdentityKeys = sourceIdentity ? buildReferenceIdentityKeys(sourceIdentity) : [];
if (!sourceResolved) {
warnings.push({
code: "unresolved-reference",
message: `unresolved flow source "${edge.source}"`,
severity: "warning",
path: diagram.path,
field: "Flows"
});
} else if (!sourceIdentityKeys.some((key) => objectIdentityKeys.has(key))) {
warnings.push({
code: "unresolved-reference",
message: `flow source "${edge.source}" is not listed in "Objects"`,
severity: "warning",
path: diagram.path,
field: "Flows"
});
}
}
if (edge.target && objectIds.has(edge.target)) {
continue;
}
const targetIdentity = edge.target ? resolveReferenceIdentity(edge.target, index) : null;
const targetResolved = !!edge.target && Boolean(targetIdentity?.resolvedModel);
const targetIdentityKeys = targetIdentity ? buildReferenceIdentityKeys(targetIdentity) : [];
if (!targetResolved) {
warnings.push({
code: "unresolved-reference",
message: `unresolved flow target "${edge.target}"`,
severity: "warning",
path: diagram.path,
field: "Flows"
});
} else if (!targetIdentityKeys.some((key) => objectIdentityKeys.has(key))) {
warnings.push({
code: "unresolved-reference",
message: `flow target "${edge.target}" is not listed in "Objects"`,
severity: "warning",
path: diagram.path,
field: "Flows"
});
}
}
} else {
for (const objectRef of diagram.objectRefs) {
if (!resolveObjectModelReference(objectRef, index) && !resolveErEntityReference(objectRef, index)) {
warnings.push({
code: "unresolved-reference",
message: `unresolved object ref "${objectRef}"`,
severity: "warning",
path: diagram.path,
field: "objectRefs"
});
}
}
}
}
function validateDfdObjectDomains(diagram, index, warnings) {
const hasDomainSources = diagram.domainSources.length > 0;
const mergedDomainIds = buildDfdMergedDomainIdSet(diagram, index, warnings);
for (const entry of diagram.objectEntries) {
const domain = entry.domain?.trim();
if (!domain) {
continue;
}
const objectId = entry.id?.trim() || entry.ref?.trim() || String(entry.rowIndex + 1);
if (mergedDomainIds.size === 0 && !hasDomainSources) {
warnings.push({
code: "unresolved-reference",
message: formatDfdObjectDomainWithoutLocalDomainsMessage(objectId, domain),
severity: "warning",
path: diagram.path,
field: "Objects.domain",
context: { rowIndex: entry.rowIndex + 1 }
});
} else if (!mergedDomainIds.has(domain)) {
warnings.push({
code: "unresolved-reference",
message: hasDomainSources ? formatDfdObjectUnknownDomainMessage(objectId, domain) : formatDfdObjectUnknownLocalDomainMessage(objectId, domain),
severity: "warning",
path: diagram.path,
field: "Objects.domain",
context: { rowIndex: entry.rowIndex + 1 }
});
}
}
}
function buildDfdMergedDomainIdSet(diagram, index, warnings) {
const domainsById = /* @__PURE__ */ new Map();
if (diagram.domainSources.length > 0) {
const resolvedSources = resolveDomainSources(
diagram.path,
diagram.domainSources,
index
);
warnings.push(...resolvedSources.warnings);
for (const domain of resolvedSources.domains) {
domainsById.set(domain.id, domain);
}
}
for (const localDomain of diagram.domains ?? []) {
const externalDomain = domainsById.get(localDomain.id);
if (externalDomain) {
for (const field of STANDALONE_DOMAIN_CANONICAL_FIELDS) {
const localValue = localDomain[field]?.trim() ?? "";
const sourceValue = externalDomain[field]?.trim() ?? "";
if (!localValue || !sourceValue || localValue === sourceValue) {
continue;
}
warnings.push({
code: "invalid-structure",
message: formatDfdLocalDomainOverridesSourceMessage(
localDomain.id,
field,
localValue,
sourceValue
),
severity: "warning",
path: diagram.path,
field: `Domains.${field}`,
context: { rowIndex: localDomain.rowIndex + 1 }
});
}
}
domainsById.set(localDomain.id, localDomain);
}
return new Set(domainsById.keys());
}
function validateDfdLocalDomains(diagram, index, warnings) {
const localDomains = diagram.domains ?? [];
if (localDomains.length === 0) {
return;
}
const sharedDomains = buildSharedDomainLookup(index);
for (const localDomain of localDomains) {
const sharedDomain = sharedDomains.get(localDomain.id);
if (!sharedDomain) {
warnings.push({
code: "unresolved-reference",
message: formatDfdLocalDomainMissingSharedMessage(localDomain.id),
severity: "warning",
path: diagram.path,
field: "Domains.id",
context: { rowIndex: localDomain.rowIndex + 1 }
});
continue;
}
compareDfdLocalDomainField(diagram.path, localDomain, sharedDomain, "name", warnings);
compareDfdLocalDomainField(diagram.path, localDomain, sharedDomain, "kind", warnings);
compareDfdLocalDomainField(diagram.path, localDomain, sharedDomain, "parent", warnings);
}
}
function buildSharedDomainLookup(index) {
const sharedDomains = /* @__PURE__ */ new Map();
for (const domainsModel of Object.values(index.domainsById)) {
for (const domain of domainsModel.domains) {
if (!sharedDomains.has(domain.id)) {
sharedDomains.set(domain.id, domain);
}
}
}
return sharedDomains;
}
function compareDfdLocalDomainField(path2, localDomain, sharedDomain, field, warnings) {
const localValue = localDomain[field]?.trim();
if (!localValue) {
return;
}
const sharedValue = sharedDomain[field]?.trim() ?? "";
if (localValue === sharedValue) {
return;
}
warnings.push({
code: "invalid-structure",
message: formatDfdLocalDomainFieldMismatchMessage(
localDomain.id,
field,
localValue,
sharedValue
),
severity: "warning",
path: path2,
field: `Domains.${field}`,
context: { rowIndex: localDomain.rowIndex + 1 }
});
}
function validateDataObject(dataObject, index, warnings) {
for (const field of dataObject.fields) {
const ref = field.ref?.trim();
if (!ref) {
continue;
}
const parsed = parseReferenceValue(ref);
if (parsed?.isExternal || parsed?.kind === "raw") {
continue;
}
const resolved = resolveReferenceIdentity(ref, index);
if (resolved.resolvedModel) {
continue;
}
warnings.push({
code: "unresolved-reference",
message: `unresolved field reference "${ref}"`,
severity: "warning",
path: dataObject.path,
field: "Fields"
});
}
}
function validateErRelationIds(index, warnings) {
const relationIdRegistry = /* @__PURE__ */ new Map();
for (const entity of Object.values(index.erEntitiesById)) {
for (const relation of entity.relationBlocks) {
const relationId = relation.id?.trim() ?? "";
if (!relationId) {
continue;
}
if (isIncompleteErRelationId3(relationId)) {
warnings.push({
code: "invalid-structure",
message: `ER relation id looks incomplete: ${relationId}`,
severity: "warning",
path: entity.path,
field: "Relations"
});
}
const existing = relationIdRegistry.get(relationId);
if (existing && (existing.path !== entity.path || existing.ownerId !== entity.id)) {
warnings.push({
code: "invalid-structure",
message: `duplicate ER relation id: ${relationId}`,
severity: "warning",
path: entity.path,
field: "Relations"
});
continue;
}
relationIdRegistry.set(relationId, { path: entity.path, ownerId: entity.id });
}
}
}
function isIncompleteErRelationId3(id) {
const normalized = id.trim().toUpperCase();
return !normalized || normalized === "REL" || normalized === "REL-" || normalized === "REL--" || normalized === "REL-NEW" || normalized === "REL-TODO";
}
function validateReservedObjectKind(object, objectId, warnings) {
if (!RESERVED_OBJECT_KINDS2.has(object.kind)) {
return;
}
warnings.push({
code: "reserved-kind-used",
message: `reserved kind used: "${object.kind}"`,
severity: "info",
path: object.path,
field: objectId
});
}
function validateRelationEndpoints(source, target, path2, index, warnings) {
if (!resolveObjectModelReference(source, index) || !resolveObjectModelReference(target, index)) {
warnings.push({
code: "unresolved-reference",
message: `unresolved relation endpoint: "${source}" -> "${target}"`,
severity: "warning",
path: path2,
field: "relations"
});
}
}
function registerId(registry, id, path2, warnings) {
const existing = registry.get(id);
if (!existing) {
registry.set(id, path2);
return;
}
warnings.push({
code: "invalid-structure",
message: `duplicate id detected: "${id}"`,
severity: "warning",
path: path2,
field: "id"
});
}
function validateFilenameMatchesId(id, path2, warnings) {
const baseName = path2.replace(/\\/g, "/").split("/").pop()?.replace(/\.md$/i, "");
if (!baseName || baseName === id) {
return;
}
warnings.push({
code: "invalid-structure",
message: `filename and id mismatch: "${baseName}" != "${id}"`,
severity: "info",
path: path2,
field: "id"
});
}
function dedupeWarnings(warnings) {
return warnings.filter((warning, index) => {
return warnings.findIndex(
(entry) => entry.code === warning.code && entry.message === warning.message && entry.path === warning.path && entry.field === warning.field
) === index;
});
}
// src/core/vault-index.ts
function buildVaultIndex(files, options = {}) {
const parseMode = options.parseMode ?? "full";
const shouldResolveRelations = options.resolveRelations ?? true;
const shouldIndexMembers = options.indexMembers ?? true;
const shouldValidate = options.validate ?? true;
const index = {
sourceFilesByPath: {},
objectsById: {},
appProcessesById: {},
screensById: {},
codesetsById: {},
messagesById: {},
rulesById: {},
mappingsById: {},
colorSchemesById: {},
domainsById: {},
domainDiagramsById: {},
dataObjectsById: {},
dfdObjectsById: {},
erEntitiesById: {},
erEntitiesByPhysicalName: {},
relationsFilesById: {},
diagramsById: {},
modelsByFilePath: {},
relationsById: {},
relationsByObjectId: {},
membersByOwnerId: {},
membersByOwnerPath: {},
warningsByFilePath: {},
state: {
relationLookupsBuilt: false,
memberLookupsBuilt: false,
vaultValidationBuilt: false,
fullParsedFilePaths: {}
}
};
for (const file of files) {
index.sourceFilesByPath[file.path] = file;
indexSingleFile(index, file, parseMode);
if (parseMode === "full") {
index.state.fullParsedFilePaths[file.path] = true;
}
}
recomputeDuplicateModelIdDiagnostics(index);
if (shouldResolveRelations) {
ensureRelationLookups(index);
}
if (shouldIndexMembers) {
ensureMemberLookups(index);
}
if (shouldValidate) {
ensureVaultValidation(index);
recomputeDuplicateModelIdDiagnostics(index);
}
return index;
}
function ensureRelationLookups(index) {
if (index.state.relationLookupsBuilt) {
return;
}
rebuildReferenceLookups(index);
index.state.relationLookupsBuilt = true;
}
function ensureMemberLookups(index) {
if (index.state.memberLookupsBuilt) {
return;
}
rebuildMemberLookups(index);
index.state.memberLookupsBuilt = true;
}
function ensureVaultValidation(index) {
if (index.state.vaultValidationBuilt) {
return;
}
clearVaultValidationWarnings(index);
clearDomainParentNotDefinedWarnings(index);
for (const warning of validateVaultIndex(index)) {
pushWarning(index.warningsByFilePath, warning.path ?? "vault", {
...warning,
context: {
...warning.context ?? {},
vaultValidation: true
}
});
}
index.state.vaultValidationBuilt = true;
recomputeDuplicateModelIdDiagnostics(index);
}
function replaceVaultIndexFile(index, file, parseMode) {
const previousModel = index.modelsByFilePath[file.path];
if (previousModel) {
removeModelFromIndexes(index, previousModel);
}
index.sourceFilesByPath[file.path] = file;
index.warningsByFilePath[file.path] = [];
indexSingleFile(index, file, parseMode);
if (parseMode === "full") {
index.state.fullParsedFilePaths[file.path] = true;
} else {
delete index.state.fullParsedFilePaths[file.path];
}
index.state.relationLookupsBuilt = false;
index.state.memberLookupsBuilt = false;
index.state.vaultValidationBuilt = false;
recomputeDuplicateModelIdDiagnostics(index);
}
function indexSingleFile(index, file, parseMode) {
const parseResult = parseVaultFile(file, parseMode);
for (const warning of parseResult.warnings) {
pushWarning(index.warningsByFilePath, file.path, {
...warning,
path: warning.path ?? file.path
});
}
if (!parseResult.file) {
return;
}
index.modelsByFilePath[file.path] = parseResult.file;
switch (parseResult.file.fileType) {
case "object": {
const objectId = getModelId2(parseResult.file);
addModelById(
index.objectsById,
objectId,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "app-process": {
addModelById(
index.appProcessesById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "screen": {
addModelById(
index.screensById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "codeset": {
addModelById(
index.codesetsById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "message": {
addModelById(
index.messagesById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "rule": {
addModelById(
index.rulesById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "mapping": {
addModelById(
index.mappingsById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "color-scheme": {
addModelById(
index.colorSchemesById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "domains": {
addModelById(
index.domainsById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "domain-diagram": {
addModelById(
index.domainDiagramsById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "relations": {
const relationsFileId = getModelId2(parseResult.file);
addModelById(
index.relationsFilesById,
relationsFileId,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
for (const relation of parseResult.file.relations) {
if (relation.id) {
addModelById(
index.relationsById,
relation.id,
relation,
index.warningsByFilePath,
file.path
);
}
}
break;
}
case "diagram": {
const diagramId = getModelId2(parseResult.file);
addModelById(
index.diagramsById,
diagramId,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "dfd-object": {
addModelById(
index.dfdObjectsById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "data-object": {
addModelById(
index.dataObjectsById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "dfd-diagram":
case "flow-diagram": {
addModelById(
index.diagramsById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
break;
}
case "er-entity": {
addModelById(
index.erEntitiesById,
parseResult.file.id,
parseResult.file,
index.warningsByFilePath,
file.path,
{ suppressDuplicateWarning: true }
);
addModelById(
index.erEntitiesByPhysicalName,
parseResult.file.physicalName,
parseResult.file,
index.warningsByFilePath,
file.path
);
break;
}
case "markdown":
break;
}
}
function rebuildReferenceLookups(index) {
index.relationsByObjectId = {};
for (const model of Object.values(index.modelsByFilePath)) {
if (model.fileType === "relations") {
for (const relation of model.relations) {
const sourceObject = resolveObjectModelReference(relation.source, index);
const targetObject = resolveObjectModelReference(relation.target, index);
addRelationForObject(
index.relationsByObjectId,
getRelationObjectKey(relation.source, sourceObject),
relation
);
addRelationForObject(
index.relationsByObjectId,
getRelationObjectKey(relation.target, targetObject),
relation
);
}
}
}
}
function rebuildMemberLookups(index) {
index.membersByOwnerId = {};
index.membersByOwnerPath = {};
for (const model of Object.values(index.modelsByFilePath)) {
switch (model.fileType) {
case "data-object":
indexDataObjectMembers(index, model);
break;
case "app-process":
indexAppProcessMembers(index, model);
break;
case "screen":
indexScreenMembers(index, model);
break;
case "codeset":
indexCodeSetMembers(index, model);
break;
case "message":
indexMessageMembers(index, model);
break;
case "rule":
indexRuleMembers(index, model);
break;
case "er-entity":
indexErEntityMembers(index, model);
break;
case "object":
indexClassMembers(index, model);
break;
default:
break;
}
}
}
function recomputeDuplicateModelIdDiagnostics(index) {
clearDuplicateModelIdWarnings(index);
const entriesByKey = /* @__PURE__ */ new Map();
for (const model of Object.values(index.modelsByFilePath)) {
const duplicateKey = getDuplicateModelKey(model);
if (!duplicateKey) {
continue;
}
if (!entriesByKey.has(duplicateKey.key)) {
entriesByKey.set(duplicateKey.key, []);
}
entriesByKey.get(duplicateKey.key).push({
id: duplicateKey.id,
path: model.path,
fileType: model.fileType
});
}
for (const entries of entriesByKey.values()) {
if (entries.length < 2) {
continue;
}
const duplicatePaths = entries.map((entry) => entry.path).sort((left, right) => left.localeCompare(right));
const first = entries[0];
if (!first) {
continue;
}
for (const entry of entries) {
pushWarning(index.warningsByFilePath, entry.path, {
code: "invalid-structure",
message: `duplicate model id detected: "${entry.id}" in ${duplicatePaths.join(", ")}`,
severity: "warning",
path: entry.path,
field: "id"
});
}
}
}
function clearDuplicateModelIdWarnings(index) {
for (const [path2, warnings] of Object.entries(index.warningsByFilePath)) {
index.warningsByFilePath[path2] = warnings.filter(
(warning) => !(warning.code === "invalid-structure" && warning.field === "id" && (warning.message.startsWith("duplicate model id detected:") || warning.message.startsWith("duplicate id detected:")))
);
}
}
function clearVaultValidationWarnings(index) {
for (const [path2, warnings] of Object.entries(index.warningsByFilePath)) {
index.warningsByFilePath[path2] = warnings.filter(
(warning) => warning.context?.vaultValidation !== true
);
}
}
function clearDomainParentNotDefinedWarnings(index) {
for (const [path2, warnings] of Object.entries(index.warningsByFilePath)) {
const model = index.modelsByFilePath[path2];
if (model?.fileType !== "domains") {
continue;
}
index.warningsByFilePath[path2] = warnings.filter(
(warning) => !getDomainParentNotDefinedValue(warning)
);
}
}
function getDomainParentNotDefinedValue(warning) {
if (warning.code !== "unresolved-reference" || warning.field !== "Domains.parent") {
return null;
}
const match = warning.message.match(/^Domain parent "([^"]+)" is not defined\.$/);
return match?.[1] ?? null;
}
function getDuplicateModelKey(model) {
if (model.fileType === "markdown") {
return null;
}
const id = getModelId2(model).trim();
if (!id) {
return null;
}
return {
key: id,
id
};
}
function parseVaultFile(file, parseMode) {
if (parseMode === "shallow") {
return parseShallowVaultFile(file);
}
const content = file.content ?? "";
const frontmatterResult = parseFrontmatter(content);
const frontmatter = frontmatterResult.file.frontmatter;
if (frontmatter?.type === "data_object") {
return parseDataObjectFile(content, file.path);
}
if (frontmatter?.type === "app_process") {
return parseAppProcessFile(content, file.path);
}
if (frontmatter?.type === "screen") {
return parseScreenFile(content, file.path);
}
if (frontmatter?.type === "codeset") {
return parseCodeSetFile(content, file.path);
}
if (frontmatter?.type === "message") {
return parseMessageFile(content, file.path);
}
if (frontmatter?.type === "rule") {
return parseRuleFile(content, file.path);
}
if (frontmatter?.type === "mapping") {
return parseMappingFile(content, file.path);
}
if (frontmatter?.type === "color_scheme") {
return parseColorSchemeFile(content, file.path);
}
if (frontmatter?.type === "domains") {
return parseDomainsFile(content, file.path);
}
if (frontmatter?.type === "domain_diagram") {
return parseDomainDiagramFile(content, file.path);
}
if (frontmatter?.type === "flow_diagram") {
return parseFlowDiagramFile(content, file.path);
}
const fileType = detectFileType(frontmatter);
switch (fileType) {
case "object":
return parseObjectFile(content, file.path);
case "dfd-object":
return parseDfdObjectFile(content, file.path);
case "app-process":
return parseAppProcessFile(content, file.path);
case "screen":
return parseScreenFile(content, file.path);
case "codeset":
return parseCodeSetFile(content, file.path);
case "message":
return parseMessageFile(content, file.path);
case "rule":
return parseRuleFile(content, file.path);
case "mapping":
return parseMappingFile(content, file.path);
case "color-scheme":
return parseColorSchemeFile(content, file.path);
case "domains":
return parseDomainsFile(content, file.path);
case "domain-diagram":
return parseDomainDiagramFile(content, file.path);
case "relations":
return parseRelationsFile(content, file.path);
case "diagram":
return parseDiagramFile(content, file.path);
case "dfd-diagram":
return parseDfdDiagramFile(content, file.path);
case "flow-diagram":
return parseFlowDiagramFile(content, file.path);
case "er-entity":
return parseErEntityFile(content, file.path);
case "markdown":
default:
return {
file: createMarkdownModel(file.path, frontmatterResult.file.body, frontmatter),
warnings: frontmatterResult.warnings
};
}
}
function parseShallowVaultFile(file) {
const frontmatterResult = file.frontmatter ? {
file: {
frontmatter: file.frontmatter,
body: ""
},
warnings: []
} : parseFrontmatter(file.content ?? "");
const frontmatter = frontmatterResult.file.frontmatter;
const fileType = detectFileType(frontmatter);
return {
file: createShallowModel(file.path, fileType, frontmatter),
warnings: frontmatterResult.warnings
};
}
function createShallowModel(path2, fileType, frontmatter) {
const common = createShallowBase(path2, frontmatter);
const id = getFrontmatterString(frontmatter, "id") ?? common.title ?? getBasename2(path2);
const name = getFrontmatterString(frontmatter, "name") ?? common.title ?? id;
const kind = getFrontmatterString(frontmatter, "kind");
switch (fileType) {
case "object":
return {
...common,
fileType: "object",
schema: "model_object_v1",
name,
kind: kind ?? "class",
attributes: [],
methods: [],
relations: []
};
case "data-object":
return {
...common,
fileType: "data-object",
schema: "data_object",
id,
name,
kind,
dataFormat: getFrontmatterString(frontmatter, "data_format"),
formatEntries: [],
records: [],
fields: [],
fieldMode: "standard"
};
case "app-process":
return {
...common,
fileType: "app-process",
schema: "app_process",
id,
name,
kind,
domains: [],
domainSources: [],
inputs: [],
outputs: [],
triggers: [],
transitions: []
};
case "screen":
return {
...common,
fileType: "screen",
schema: "screen",
id,
name,
screenType: getFrontmatterString(frontmatter, "screen_type"),
layouts: [],
fields: [],
actions: [],
messages: [],
localProcesses: [],
legacyTransitions: []
};
case "codeset":
return {
...common,
fileType: "codeset",
schema: "codeset",
id,
name,
kind,
values: []
};
case "message":
return {
...common,
fileType: "message",
schema: "message",
id,
name,
kind,
messages: []
};
case "rule":
return {
...common,
fileType: "rule",
schema: "rule",
id,
name,
kind,
inputs: [],
references: [],
messages: []
};
case "mapping":
return {
...common,
fileType: "mapping",
schema: "mapping",
id,
name,
kind,
source: getFrontmatterString(frontmatter, "source"),
target: getFrontmatterString(frontmatter, "target"),
scope: [],
mappings: []
};
case "color-scheme":
return {
...common,
fileType: "color-scheme",
schema: "color_scheme",
id,
name,
colors: []
};
case "domains":
return {
...common,
fileType: "domains",
schema: "domains",
id,
name,
description: getFrontmatterString(frontmatter, "description"),
domains: []
};
case "domain-diagram":
return {
...common,
fileType: "domain-diagram",
schema: "domain_diagram",
id,
name,
domainSources: []
};
case "dfd-object":
return {
...common,
fileType: "dfd-object",
schema: "dfd_object",
id,
name,
kind: kind ?? "process"
};
case "relations":
return {
...common,
fileType: "relations",
schema: "model_relations_v1",
relations: []
};
case "diagram": {
const diagramKind = kind === "er" ? "er" : "class";
return {
...common,
fileType: "diagram",
schema: diagramKind === "er" ? "er_diagram" : "class_diagram",
name,
kind: diagramKind,
objectRefs: [],
nodes: [],
edges: []
};
}
case "dfd-diagram":
return {
...common,
fileType: "dfd-diagram",
schema: "dfd_diagram",
id,
name,
kind: "dfd",
domainSources: [],
domains: [],
objectRefs: [],
objectEntries: [],
nodes: [],
edges: [],
flows: []
};
case "flow-diagram":
return {
...common,
fileType: "flow-diagram",
schema: "flow_diagram",
id,
name,
kind: "screen_communication",
flowView: "detail",
flowViewSpecified: false,
domainSources: [],
domains: [],
objectRefs: [],
objectEntries: [],
nodes: [],
edges: [],
flows: []
};
case "er-entity":
return {
...common,
fileType: "er-entity",
id,
filePath: path2,
logicalName: getFrontmatterString(frontmatter, "logical_name") ?? name,
physicalName: getFrontmatterString(frontmatter, "physical_name") ?? id,
schemaName: getFrontmatterString(frontmatter, "schema_name") ?? null,
dbms: getFrontmatterString(frontmatter, "dbms") ?? null,
columns: [],
indexes: [],
relationBlocks: [],
outboundRelations: []
};
case "markdown":
default:
return {
...common,
fileType: "markdown",
content: ""
};
}
}
function createShallowBase(path2, frontmatter) {
return {
path: path2,
title: getFrontmatterString(frontmatter, "title"),
frontmatter: frontmatter ?? {},
sections: {},
sourceLinks: []
};
}
function getFrontmatterString(frontmatter, key) {
const value = frontmatter?.[key];
return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function createMarkdownModel(path2, body, frontmatter) {
return {
fileType: "markdown",
path: path2,
title: typeof frontmatter?.title === "string" ? frontmatter.title : void 0,
frontmatter: frontmatter ?? {},
sections: extractMarkdownSections(body),
sourceLinks: [],
content: body
};
}
function indexDataObjectMembers(index, model) {
const ownerId = getModelId2(model);
for (const field of model.fields) {
const memberId = field.name?.trim();
if (!memberId) {
continue;
}
const displayName = field.recordType?.trim() ? `${field.label?.trim() || memberId} (${field.recordType.trim()})` : field.label?.trim() || memberId;
addMemberCandidate(index, {
ownerModelType: "data_object",
ownerId,
ownerPath: model.path,
memberKind: "field",
memberId,
displayName,
sourceSection: "Fields"
});
}
}
function indexAppProcessMembers(index, model) {
const ownerId = getModelId2(model);
for (const input of model.inputs) {
const memberId = input.id?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "app_process",
ownerId,
ownerPath: model.path,
memberKind: "input",
memberId,
displayName: memberId,
sourceSection: "Inputs"
});
}
for (const output of model.outputs) {
const memberId = output.id?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "app_process",
ownerId,
ownerPath: model.path,
memberKind: "output",
memberId,
displayName: memberId,
sourceSection: "Outputs"
});
}
}
function indexScreenMembers(index, model) {
const ownerId = getModelId2(model);
for (const field of model.fields) {
const memberId = field.id?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "screen",
ownerId,
ownerPath: model.path,
memberKind: "field",
memberId,
displayName: field.label?.trim() || memberId,
sourceSection: "Fields"
});
}
for (const action of model.actions) {
const memberId = action.id?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "screen",
ownerId,
ownerPath: model.path,
memberKind: "action",
memberId,
displayName: action.label?.trim() || memberId,
sourceSection: "Actions"
});
}
}
function indexCodeSetMembers(index, model) {
const ownerId = getModelId2(model);
for (const value of model.values) {
const memberId = value.code?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "codeset",
ownerId,
ownerPath: model.path,
memberKind: "code",
memberId,
displayName: value.label?.trim() || memberId,
sourceSection: "Values"
});
}
}
function indexMessageMembers(index, model) {
const ownerId = getModelId2(model);
for (const message of model.messages) {
const memberId = message.messageId?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "message",
ownerId,
ownerPath: model.path,
memberKind: "message",
memberId,
displayName: message.text?.trim() || memberId,
sourceSection: "Messages"
});
}
}
function indexRuleMembers(index, model) {
const ownerId = getModelId2(model);
for (const input of model.inputs) {
const memberId = input.id?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "rule",
ownerId,
ownerPath: model.path,
memberKind: "input",
memberId,
displayName: memberId,
sourceSection: "Inputs"
});
}
}
function indexErEntityMembers(index, model) {
const ownerId = getModelId2(model);
for (const column of model.columns) {
const memberId = column.physicalName?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "er_entity",
ownerId,
ownerPath: model.path,
memberKind: "column",
memberId,
displayName: column.logicalName?.trim() || memberId,
sourceSection: "Columns"
});
}
}
function indexClassMembers(index, model) {
const ownerId = getModelId2(model);
for (const attribute of model.attributes) {
const memberId = attribute.name?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "class",
ownerId,
ownerPath: model.path,
memberKind: "attribute",
memberId,
displayName: memberId,
sourceSection: "Attributes"
});
}
for (const method of model.methods) {
const memberId = method.name?.trim();
if (!memberId) {
continue;
}
addMemberCandidate(index, {
ownerModelType: "class",
ownerId,
ownerPath: model.path,
memberKind: "method",
memberId,
displayName: memberId,
sourceSection: "Methods"
});
}
}
function addModelById(target, id, model, warningsByFilePath, path2, options = {}) {
if (!target[id]) {
target[id] = model;
return;
}
if (options.suppressDuplicateWarning) {
return;
}
pushWarning(warningsByFilePath, path2, {
code: "invalid-structure",
message: `duplicate id detected: "${id}"`,
severity: "warning",
path: path2,
field: "id"
});
}
function removeModelFromIndexes(index, model) {
delete index.modelsByFilePath[model.path];
switch (model.fileType) {
case "object":
delete index.objectsById[getModelId2(model)];
break;
case "app-process":
delete index.appProcessesById[model.id];
break;
case "screen":
delete index.screensById[model.id];
break;
case "codeset":
delete index.codesetsById[model.id];
break;
case "message":
delete index.messagesById[model.id];
break;
case "rule":
delete index.rulesById[model.id];
break;
case "mapping":
delete index.mappingsById[model.id];
break;
case "color-scheme":
delete index.colorSchemesById[model.id];
break;
case "domains":
delete index.domainsById[model.id];
break;
case "domain-diagram":
delete index.domainDiagramsById[model.id];
break;
case "relations":
delete index.relationsFilesById[getModelId2(model)];
for (const relation of model.relations) {
if (relation.id) {
delete index.relationsById[relation.id];
}
}
break;
case "diagram":
delete index.diagramsById[getModelId2(model)];
break;
case "dfd-object":
delete index.dfdObjectsById[model.id];
break;
case "data-object":
delete index.dataObjectsById[model.id];
break;
case "dfd-diagram":
case "flow-diagram":
delete index.diagramsById[model.id];
break;
case "er-entity":
delete index.erEntitiesById[model.id];
delete index.erEntitiesByPhysicalName[model.physicalName];
break;
case "markdown":
break;
}
}
function addRelationForObject(relationsByObjectId, objectId, relation) {
if (!objectId.trim()) {
return;
}
if (!relationsByObjectId[objectId]) {
relationsByObjectId[objectId] = [];
}
relationsByObjectId[objectId].push(relation);
}
function addMemberCandidate(index, candidate) {
if (!candidate.ownerId.trim() || !candidate.ownerPath.trim() || !candidate.memberId.trim()) {
return;
}
pushMemberCandidate(index.membersByOwnerId, candidate.ownerId, candidate);
pushMemberCandidate(index.membersByOwnerPath, candidate.ownerPath, candidate);
}
function pushMemberCandidate(target, key, candidate) {
if (!target[key]) {
target[key] = [];
}
const exists = target[key].some(
(entry) => entry.ownerPath === candidate.ownerPath && entry.memberKind === candidate.memberKind && entry.memberId === candidate.memberId && entry.sourceSection === candidate.sourceSection && entry.displayName === candidate.displayName
);
if (!exists) {
target[key].push(candidate);
}
}
function getRelationObjectKey(rawReference, object) {
if (object) {
return getModelId2(object);
}
return rawReference.trim();
}
function getModelId2(model) {
if ("id" in model && typeof model.id === "string" && model.id.trim()) {
return model.id.trim();
}
const explicitId = model.frontmatter.id;
if (typeof explicitId === "string" && explicitId.trim()) {
return explicitId.trim();
}
if ("name" in model && typeof model.name === "string" && model.name.trim()) {
return model.name.trim();
}
return getBasename2(model.path);
}
function getBasename2(path2) {
const slashNormalized = path2.replace(/\\/g, "/");
const rawName = slashNormalized.split("/").pop() ?? path2;
return rawName.replace(/\.md$/i, "");
}
function pushWarning(warningsByFilePath, path2, warning) {
if (!warningsByFilePath[path2]) {
warningsByFilePath[path2] = [];
}
const exists = warningsByFilePath[path2].some(
(entry) => entry.code === warning.code && entry.message === warning.message && entry.field === warning.field
);
if (!exists) {
warningsByFilePath[path2].push(warning);
}
}
// src/utils/model-navigation.ts
var import_obsidian5 = require("obsidian");
async function openModelObjectNote(app, index, objectId, options = {}) {
const model = index.objectsById[objectId] ?? index.erEntitiesById[objectId] ?? index.dfdObjectsById[objectId];
if (!model) {
return {
ok: false,
reason: `Object "${objectId}" was not found in the current index.`
};
}
const file = app.vault.getAbstractFileByPath(model.path);
if (!(file instanceof import_obsidian5.TFile)) {
return {
ok: false,
reason: `Note for object "${objectId}" could not be opened.`
};
}
const leaf = options.openInNewLeaf ? app.workspace.getLeaf(true) : findExistingMarkdownLeaf(app, options.sourcePath) ?? app.workspace.getMostRecentLeaf();
if (!leaf) {
return {
ok: false,
reason: `No target tab was available to open "${objectId}".`
};
}
await leaf.openFile(file);
return { ok: true };
}
function findExistingMarkdownLeaf(app, sourcePath) {
if (!sourcePath) {
return null;
}
const markdownLeaves = app.workspace.getLeavesOfType("markdown");
for (const leaf of markdownLeaves) {
const viewFile = leaf.view.file ?? null;
if (viewFile?.path === sourcePath) {
return leaf;
}
}
return null;
}
// src/renderers/mermaid-helpers.ts
function sanitizeMermaidId(input) {
const normalized = (input || "node").replace(/[^A-Za-z0-9_]/g, "_");
if (/^[A-Za-z_]/.test(normalized)) {
return normalized;
}
return `N_${normalized}`;
}
function ensureUniqueMermaidId(baseId, usedIds) {
let candidate = baseId || "node";
let index = 2;
while (usedIds.has(candidate)) {
candidate = `${baseId}_${index}`;
index += 1;
}
usedIds.add(candidate);
return candidate;
}
function escapeMermaidLabel(input) {
return splitMermaidTextLines(input).map(escapeMermaidTextSegment).join("
");
}
function escapeMermaidEdgeLabel(input) {
return escapeMermaidTextSegment(input).replace(/[[\]{}()]/g, " ").replace(/\s+/g, " ").trim();
}
function toMermaidQuotedLabel(input) {
return `"${splitMermaidTextLines(input).map(escapeMermaidQuotedTextSegment).join("
")}"`;
}
function formatMermaidMember(input) {
return escapeMermaidTextSegment(input).replace(/\s+/g, " ").trim();
}
function splitMermaidTextLines(input) {
return String(input).replace(/\r\n?/g, "\n").split("\n");
}
function escapeMermaidTextSegment(input) {
return String(input).replace(/&/g, "&").replace(/"/g, """).replace(/\|/g, "/").replace(//g, ">").replace(/\[/g, "[").replace(/\]/g, "]");
}
function escapeMermaidQuotedTextSegment(input) {
return String(input).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(//g, ">").replace(/\[/g, "#91;").replace(/\]/g, "#93;");
}
// src/renderers/weave-map-mermaid.ts
var WEAVE_MAP_LAYER_ORDER = [
"UI",
"Process",
"Rule",
"Rule / State",
"UI / Message",
"Data",
"Mapping",
"Implementation",
"Data Flow",
"Relationship",
"Source",
"Warning",
"Other"
];
var DEFAULT_WEAVE_MAP_LAYER_STYLES = {
UI: { fill: "#eef6ff", stroke: "#b8d4f0", color: "#1f2937" },
Process: { fill: "#eefaf1", stroke: "#b7dfc2", color: "#1f2937" },
Rule: { fill: "#fff8e6", stroke: "#ead38a", color: "#1f2937" },
"Rule / State": { fill: "#fff8e6", stroke: "#ead38a", color: "#1f2937" },
"UI / Message": { fill: "#fdf2f8", stroke: "#f0b8d4", color: "#1f2937" },
Data: { fill: "#eef6ff", stroke: "#b8d4f0", color: "#1f2937" },
Mapping: { fill: "#f5efff", stroke: "#d6c2f0", color: "#1f2937" },
Implementation: { fill: "#f3f4f6", stroke: "#cbd5e1", color: "#1f2937" },
"Data Flow": { fill: "#ecfeff", stroke: "#a5dbe2", color: "#1f2937" },
Relationship: { fill: "#f8fafc", stroke: "#cbd5e1", color: "#1f2937" },
Source: { fill: "#effaf0", stroke: "#9fd3a8", color: "#1f2937" },
Warning: { fill: "#fff1f1", stroke: "#f0b4b4", color: "#1f2937" },
Other: { fill: "#f7f7f7", stroke: "#d4d4d8", color: "#1f2937" }
};
function buildWeaveMapMermaidSource(model, options = {}) {
const nodeIds = createWeaveMapNodeMermaidIds(model.nodes);
const orderedLayers = getOrderedLayers(model.nodes);
const lines = [
"flowchart LR",
` ${buildWeaveMapClassDef("weaveFocus", "#fff3cd", "#d39e00", 2.4)}`,
` ${buildWeaveMapClassDef("weaveNode", "#f5f7fb", "#7c8a9a")}`,
` ${buildWeaveMapClassDef("weaveSource", "#e8f5e9", "#388e3c")}`,
` ${buildWeaveMapClassDef("weaveUnresolved", "#ffebee", "#c62828", 2)}`,
` ${buildWeaveMapClassDef("weaveWarning", "#fff8e1", "#f57f17", 2)}`,
""
];
for (const layer of orderedLayers) {
const layerNodes = model.nodes.filter((node) => node.layer === layer);
if (layerNodes.length === 0) {
continue;
}
const subgraphId = `layer_${sanitizeMermaidId(layer)}`;
lines.push(` subgraph ${subgraphId}["${escapeMermaidLabel(layer)}"]`);
for (const node of layerNodes) {
const mermaidId = nodeIds.get(node.id) ?? sanitizeMermaidId(node.id);
lines.push(` ${mermaidId}["${buildNodeLabel(node)}"]`);
}
lines.push(" end", "");
}
for (const layer of orderedLayers) {
lines.push(` ${buildLayerStyleLine(layer, options.colorScheme)}`);
}
if (orderedLayers.length > 0) {
lines.push("");
}
for (const edge of model.edges) {
const from = nodeIds.get(edge.from) ?? sanitizeMermaidId(edge.from);
const to = nodeIds.get(edge.to) ?? sanitizeMermaidId(edge.to);
const label = sanitizeEdgeLabel(edge.label || edge.relationType);
const arrow = edge.status === "unresolved" ? "-.->" : "-->";
lines.push(` ${from} ${arrow}|${label}| ${to}`);
}
if (model.edges.length > 0) {
lines.push("");
}
for (const node of model.nodes) {
const mermaidId = nodeIds.get(node.id) ?? sanitizeMermaidId(node.id);
lines.push(` class ${mermaidId} ${getNodeClassName(node)}`);
}
return lines.join("\n").trimEnd();
}
function createWeaveMapNodeMermaidIds(nodes) {
const usedIds = /* @__PURE__ */ new Set();
const ids = /* @__PURE__ */ new Map();
for (const node of nodes) {
ids.set(node.id, ensureUniqueMermaidId(`n_${sanitizeMermaidId(node.id)}`, usedIds));
}
return ids;
}
function getOrderedLayers(nodes) {
const presentLayers = new Set(nodes.map((node) => node.layer));
const ordered = WEAVE_MAP_LAYER_ORDER.filter((layer) => presentLayers.has(layer));
const extra = Array.from(presentLayers).filter((layer) => !WEAVE_MAP_LAYER_ORDER.includes(layer));
return [...ordered, ...extra];
}
function buildNodeLabel(node) {
return escapeMermaidLabel(`${node.layer}
${node.label}`);
}
function sanitizeEdgeLabel(label) {
return escapeMermaidEdgeLabel(label) || "relates";
}
function buildLayerStyleLine(layer, colorScheme) {
const style = resolveWeaveMapLayerStyle(layer, colorScheme);
return `style layer_${sanitizeMermaidId(layer)} fill:${style.fill},stroke:${style.stroke},stroke-width:1px,color:${style.color}`;
}
function resolveWeaveMapLayerStyle(layer, colorScheme) {
const fallback = DEFAULT_WEAVE_MAP_LAYER_STYLES[layer] ?? DEFAULT_WEAVE_MAP_LAYER_STYLES.Other;
const override = colorScheme?.entries.find(
(entry) => (entry.target?.trim().toLowerCase() ?? "") === "weave_map" && entry.kind.trim().toLowerCase() === getWeaveMapLayerColorKind(layer)
);
if (!override) {
return fallback;
}
return {
fill: override.fill ?? fallback.fill,
stroke: override.stroke ?? fallback.stroke,
color: override.text ?? fallback.color
};
}
function getWeaveMapLayerColorKind(layer) {
switch (layer) {
case "UI":
return "ui";
case "Process":
return "process";
case "Rule":
return "rule";
case "Rule / State":
return "rule_state";
case "UI / Message":
return "ui_message";
case "Data":
return "data";
case "Mapping":
return "mapping";
case "Implementation":
return "implementation";
case "Data Flow":
return "data_flow";
case "Relationship":
return "relationship";
case "Source":
return "source";
case "Warning":
return "warning";
case "Other":
return "other";
}
}
function getNodeClassName(node) {
if (node.status === "focus") {
return "weaveFocus";
}
if (node.status === "source") {
return "weaveSource";
}
if (node.status === "unresolved") {
return "weaveUnresolved";
}
if (node.status === "warning") {
return "weaveWarning";
}
return "weaveNode";
}
function buildWeaveMapClassDef(className, fill, stroke, strokeWidth = 1.4) {
return `classDef ${className} fill:${fill},stroke:${stroke},color:#111111,stroke-width:${strokeWidth}px`;
}
// src/views/modeling-preview-view.ts
var import_obsidian7 = require("obsidian");
var import_electron2 = require("electron");
// src/core/object-subgraph-builder.ts
function buildObjectSubgraphScene(context) {
const centerId = getFocusObjectId(context.object);
const nodes = /* @__PURE__ */ new Map();
const edges = /* @__PURE__ */ new Map();
nodes.set(centerId, {
id: centerId,
ref: centerId,
object: context.object
});
for (const entry of context.relatedObjects) {
if (entry.relatedObject) {
nodes.set(entry.relatedObjectId, {
id: entry.relatedObjectId,
ref: entry.relatedObjectId,
object: entry.relatedObject
});
}
const edge = toDiagramEdge2(entry, centerId);
if (!edge) {
continue;
}
const edgeKey = edge.id ?? `${edge.source}:${edge.target}:${edge.kind ?? ""}:${edge.label ?? ""}`;
edges.set(edgeKey, edge);
}
const kind = context.object.fileType === "er-entity" ? "er" : "class";
const diagram = {
fileType: "diagram",
schema: kind === "er" ? "er_diagram" : "class_diagram",
path: context.object.path,
title: `${getGraphTitle(context.object)} related`,
frontmatter: {
name: `${getGraphTitle(context.object)} related`
},
sections: {},
sourceLinks: [],
name: `${getGraphTitle(context.object)} related`,
kind,
objectRefs: Array.from(nodes.keys()),
nodes: Array.from(nodes.values()).map(({ object, ...node }) => node),
edges: Array.from(edges.values())
};
return {
diagram,
nodes: Array.from(nodes.values()),
edges: Array.from(edges.values()),
missingObjects: [],
warnings: []
};
}
function toDiagramEdge2(entry, centerId) {
const relatedId = entry.relatedObjectId;
if (entry.relation && "domain" in entry.relation && entry.relation.domain === "er") {
const relation2 = entry.relation;
const sourceId2 = entry.direction === "incoming" ? relatedId : centerId;
const targetId2 = entry.direction === "incoming" ? centerId : relatedId;
return {
id: relation2.id,
source: sourceId2,
target: targetId2,
kind: "association",
label: relation2.label,
metadata: {
cardinality: relation2.cardinality,
sourceColumn: relation2.mappings[0]?.localColumn,
targetColumn: relation2.mappings[0]?.targetColumn,
logicalName: relation2.label,
physicalName: relation2.id,
kind: relation2.kind,
mappingSummary: relation2.mappings.map((mapping) => `${mapping.localColumn} -> ${mapping.targetColumn}`).join(" / "),
mappings: relation2.mappings
}
};
}
const relation = normalizeClassRelation(entry.relation);
const sourceId = entry.direction === "incoming" ? relatedId : centerId;
const targetId = entry.direction === "incoming" ? centerId : relatedId;
return {
id: relation.id,
source: sourceId,
target: targetId,
kind: relation.kind,
label: relation.label,
metadata: {
notes: relation.notes,
sourceCardinality: relation.fromMultiplicity,
targetCardinality: relation.toMultiplicity
}
};
}
function normalizeClassRelation(relation) {
if ("domain" in relation) {
if (relation.domain === "class") {
return relation;
}
return toClassRelationEdge({
id: relation.id,
kind: "association",
source: relation.source,
target: relation.target,
label: relation.label
});
}
return toClassRelationEdge(relation);
}
function getGraphTitle(object) {
return object.fileType === "er-entity" ? object.logicalName : object.name;
}
function getObjectId4(object) {
const explicitId = object.frontmatter.id;
if (typeof explicitId === "string" && explicitId.trim()) {
return explicitId.trim();
}
return object.name;
}
function getFocusObjectId(object) {
return object.fileType === "er-entity" ? object.id : getObjectId4(object);
}
// src/core/app-process-flow-editor.ts
var FLOWS_HEADER = "| from | to | condition | label | notes |";
var FLOWS_SEPARATOR = "|---|---|---|---|---|";
function addAppProcessFlow(markdown, input) {
const from = input.from.trim();
const to = input.to.trim();
if (!from || !to) {
return unchanged(markdown, "invalid");
}
const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
const normalized = markdown.replace(/\r\n/g, "\n");
const lines = normalized.split("\n");
const hadFinalNewline = normalized.endsWith("\n");
if (hadFinalNewline) {
lines.pop();
}
const steps = findSection(lines, "Steps");
if (!steps) {
return unchanged(markdown, "missing-steps");
}
const flows = findSection(lines, "Flows");
if (flows) {
if (hasDuplicateFlow(lines, flows, from, to)) {
return unchanged(markdown, "duplicate");
}
const insertIndex = findExistingFlowsAppendIndex(lines, flows);
lines.splice(insertIndex, 0, formatFlowRow(from, to));
} else {
const insertLines = [
"",
"## Flows",
"",
FLOWS_HEADER,
FLOWS_SEPARATOR,
formatFlowRow(from, to),
""
];
lines.splice(steps.endIndex, 0, ...insertLines);
}
const updated = lines.join("\n") + (hadFinalNewline ? "\n" : "");
return {
updatedMarkdown: newline === "\r\n" ? updated.replace(/\n/g, "\r\n") : updated,
changed: true,
status: "added"
};
}
function unchanged(markdown, status) {
return {
updatedMarkdown: markdown,
changed: false,
status
};
}
function findSection(lines, heading) {
const target = heading.trim().toLowerCase();
for (let index = 0; index < lines.length; index += 1) {
const match = lines[index].match(/^##\s+(.+?)\s*$/);
if (!match || match[1].trim().toLowerCase() !== target) {
continue;
}
let endIndex = lines.length;
for (let next = index + 1; next < lines.length; next += 1) {
if (/^##\s+/.test(lines[next])) {
endIndex = next;
break;
}
}
return {
headingIndex: index,
contentStartIndex: index + 1,
endIndex
};
}
return null;
}
function hasDuplicateFlow(lines, flows, from, to) {
for (let index = flows.contentStartIndex; index < flows.endIndex; index += 1) {
const cells = parseMarkdownTableRow2(lines[index]);
if (!cells || isSeparatorRow2(cells)) {
continue;
}
if (cells[0] === "from" && cells[1] === "to") {
continue;
}
if (cells[0] === from && cells[1] === to) {
return true;
}
}
return false;
}
function findExistingFlowsAppendIndex(lines, flows) {
let appendIndex = flows.endIndex;
for (let index = flows.contentStartIndex; index < flows.endIndex; index += 1) {
if (parseMarkdownTableRow2(lines[index])) {
appendIndex = index + 1;
}
}
return appendIndex;
}
function parseMarkdownTableRow2(line) {
const trimmed = line.trim();
if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) {
return null;
}
return trimmed.slice(1, -1).split("|").map((cell) => cell.trim());
}
function isSeparatorRow2(cells) {
return cells.every((cell) => /^:?-{3,}:?$/.test(cell));
}
function formatFlowRow(from, to) {
return "| " + from + " | " + to + " | | | |";
}
// src/core/domain-tree.ts
function buildDomainTree(domains) {
const nodes = /* @__PURE__ */ new Map();
const roots = [];
for (const domain of domains) {
nodes.set(domain.id, {
domain,
children: []
});
}
for (const domain of domains) {
const node = nodes.get(domain.id);
if (!node) {
continue;
}
const parent = domain.parent ? nodes.get(domain.parent) : void 0;
if (!parent || domain.parent === domain.id || hasAncestor(parent, domain.id, nodes)) {
roots.push(node);
continue;
}
parent.children.push(node);
}
return roots;
}
function hasAncestor(node, targetId, nodes) {
const seen = /* @__PURE__ */ new Set();
let current = node;
while (current?.domain.parent) {
if (current.domain.id === targetId || seen.has(current.domain.id)) {
return true;
}
seen.add(current.domain.id);
current = nodes.get(current.domain.parent);
}
return current?.domain.id === targetId;
}
// src/renderers/graph-layout.ts
function buildGraphLayout(nodes, edges, options) {
if (nodes.length === 0) {
return {
nodes: [],
byId: {},
width: options.canvasPadding * 2,
height: options.canvasPadding * 2
};
}
const nodeSizes = /* @__PURE__ */ new Map();
const originalIndex = /* @__PURE__ */ new Map();
for (const [index, node] of nodes.entries()) {
nodeSizes.set(node.id, {
width: options.getWidth(node),
height: options.getHeight(node)
});
originalIndex.set(node.id, index);
}
const maxWidth = Math.max(...nodes.map((node) => nodeSizes.get(node.id)?.width ?? 0));
const maxHeight = Math.max(...nodes.map((node) => nodeSizes.get(node.id)?.height ?? 0));
const columnCount = clamp2(
deriveColumnCount(nodes.length),
options.minColumns ?? 1,
options.maxColumns ?? 4
);
const rowCount = Math.ceil(nodes.length / columnCount);
const cellWidth = maxWidth + options.columnGap;
const cellHeight = maxHeight + options.rowGap;
const degrees = buildDegreeMap(nodes, edges);
const neighborMap = buildNeighborMap(nodes, edges, originalIndex);
const sortedNodes = [...nodes].sort((left, right) => {
const degreeDelta = (degrees.get(right.id) ?? 0) - (degrees.get(left.id) ?? 0);
if (degreeDelta !== 0) {
return degreeDelta;
}
const barycenterDelta = getNeighborBarycenter(left.id, neighborMap) - getNeighborBarycenter(right.id, neighborMap);
if (barycenterDelta !== 0) {
return barycenterDelta;
}
return (originalIndex.get(left.id) ?? 0) - (originalIndex.get(right.id) ?? 0);
});
const slots = buildSlots(columnCount, rowCount);
const slotAssignments = /* @__PURE__ */ new Map();
for (const [index, node] of sortedNodes.entries()) {
const slot = slots[index];
if (slot) {
slotAssignments.set(node.id, slot);
}
}
optimizeAssignments(slotAssignments, nodes, edges, columnCount);
const layouts = [];
const byId = {};
for (const node of nodes) {
const slot = slotAssignments.get(node.id);
if (!slot) {
continue;
}
const size = nodeSizes.get(node.id) ?? { width: maxWidth, height: maxHeight };
const x = options.canvasPadding + slot.col * cellWidth + Math.max(0, (maxWidth - size.width) / 2);
const y = options.canvasPadding + slot.row * cellHeight + Math.max(0, (maxHeight - size.height) / 2);
const layout = {
node,
x,
y,
width: size.width,
height: size.height
};
layouts.push(layout);
byId[node.id] = layout;
}
return {
nodes: layouts,
byId,
width: options.canvasPadding * 2 + columnCount * maxWidth + Math.max(0, columnCount - 1) * options.columnGap,
height: options.canvasPadding * 2 + rowCount * maxHeight + Math.max(0, rowCount - 1) * options.rowGap
};
}
function deriveColumnCount(nodeCount) {
if (nodeCount >= 10) {
return 4;
}
if (nodeCount >= 5) {
return 3;
}
if (nodeCount >= 2) {
return 2;
}
return 1;
}
function buildDegreeMap(nodes, edges) {
const degrees = /* @__PURE__ */ new Map();
for (const node of nodes) {
degrees.set(node.id, 0);
}
for (const edge of edges) {
degrees.set(edge.source, (degrees.get(edge.source) ?? 0) + 1);
degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
}
return degrees;
}
function buildNeighborMap(nodes, edges, originalIndex) {
const neighborMap = /* @__PURE__ */ new Map();
for (const node of nodes) {
neighborMap.set(node.id, []);
}
for (const edge of edges) {
neighborMap.get(edge.source)?.push(originalIndex.get(edge.target) ?? 0);
neighborMap.get(edge.target)?.push(originalIndex.get(edge.source) ?? 0);
}
return neighborMap;
}
function getNeighborBarycenter(nodeId, neighborMap) {
const indices = neighborMap.get(nodeId) ?? [];
if (indices.length === 0) {
return Number.MAX_SAFE_INTEGER;
}
return indices.reduce((sum, value) => sum + value, 0) / indices.length;
}
function buildSlots(columnCount, rowCount) {
const centerColumn = (columnCount - 1) / 2;
const centerRow = (rowCount - 1) / 2;
const slots = [];
for (let row = 0; row < rowCount; row += 1) {
for (let col = 0; col < columnCount; col += 1) {
const centerDistance = Math.abs(col - centerColumn) * 1.2 + Math.abs(row - centerRow);
slots.push({ col, row, centerDistance });
}
}
return slots.sort((left, right) => {
if (left.centerDistance !== right.centerDistance) {
return left.centerDistance - right.centerDistance;
}
if (left.row !== right.row) {
return left.row - right.row;
}
return left.col - right.col;
});
}
function optimizeAssignments(assignments, nodes, edges, columnCount) {
const orderedIds = [...nodes.map((node) => node.id)].sort((left, right) => {
const leftSlot = assignments.get(left);
const rightSlot = assignments.get(right);
if (!leftSlot || !rightSlot) {
return 0;
}
if (leftSlot.row !== rightSlot.row) {
return leftSlot.row - rightSlot.row;
}
return leftSlot.col - rightSlot.col;
});
for (let pass = 0; pass < 2; pass += 1) {
for (let index = 0; index < orderedIds.length - 1; index += 1) {
const leftId = orderedIds[index];
const rightId = orderedIds[index + 1];
const leftSlot = assignments.get(leftId);
const rightSlot = assignments.get(rightId);
if (!leftSlot || !rightSlot) {
continue;
}
const rowGap = Math.abs(leftSlot.row - rightSlot.row);
const colGap = Math.abs(leftSlot.col - rightSlot.col);
if (rowGap + colGap !== 1) {
continue;
}
const currentCost = estimateLayoutCost(assignments, edges, columnCount);
assignments.set(leftId, rightSlot);
assignments.set(rightId, leftSlot);
const swappedCost = estimateLayoutCost(assignments, edges, columnCount);
if (swappedCost >= currentCost) {
assignments.set(leftId, leftSlot);
assignments.set(rightId, rightSlot);
}
}
}
}
function estimateLayoutCost(assignments, edges, columnCount) {
let cost = 0;
for (const edge of edges) {
const source = assignments.get(edge.source);
const target = assignments.get(edge.target);
if (!source || !target) {
continue;
}
const dx = Math.abs(source.col - target.col);
const dy = Math.abs(source.row - target.row);
cost += dx * 3 + dy * 2;
if (dx > Math.max(1, columnCount - 2)) {
cost += 2;
}
}
return cost;
}
function clamp2(value, min, max) {
return Math.min(max, Math.max(min, value));
}
// src/views/mermaid-node-interactions.ts
var HOVER_POPOVER_SELECTOR = ".hover-popover";
var FOCUS_MODE_OVERLAY_SELECTOR = ".model-weave-viewer-focus-mode";
function attachMermaidNodeInteractions(options) {
if (options.targets.length === 0) {
return () => void 0;
}
const svg = options.svg ?? options.rootEl.querySelector("svg");
if (!svg) {
return () => void 0;
}
const controller = new AbortController();
const dragThreshold = options.dragThreshold ?? 6;
const source = options.source ?? "model-weave";
const nodeSelector = options.nodeSelector ?? "g.node";
const interactions = buildMermaidNodeInteractions(svg, options.targets, nodeSelector, options.findNodeElements);
let pointerStart = null;
let pendingOpen = null;
let lastPointerupOpen = null;
const hoverState = createGraphHoverState();
for (const interaction of interactions) {
if (options.nodeClassName) {
interaction.nodeEl.classList.add(options.nodeClassName);
}
setMermaidNodeTitle(interaction.nodeEl, interaction.target, options.formatTitle);
}
options.rootEl.addEventListener("pointerdown", (event) => {
const interaction = getMermaidNodeInteractionFromEvent(event, interactions);
pointerStart = interaction ? {
x: event.clientX,
y: event.clientY,
mermaidId: interaction.target.mermaidId
} : null;
pendingOpen = interaction ? {
pointerId: event.pointerId,
startX: event.clientX,
startY: event.clientY,
target: interaction.target,
opened: false,
canceled: false
} : null;
logMermaidInteractionClickDebug(options, "pointerdown", event, interaction, void 0, false, Boolean(interaction), false);
}, { signal: controller.signal });
const documentEl = options.rootEl.ownerDocument;
documentEl.addEventListener("pointermove", (event) => {
if (!pendingOpen || pendingOpen.pointerId !== event.pointerId) {
return;
}
const distance = getPendingMermaidOpenDistance(pendingOpen, event);
if (distance > dragThreshold) {
pendingOpen.canceled = true;
}
}, { signal: controller.signal });
documentEl.addEventListener("pointerup", (event) => {
const pending = pendingOpen;
if (!pending || pending.pointerId !== event.pointerId) {
return;
}
const distance = getPendingMermaidOpenDistance(pending, event);
const isDrag = distance > dragThreshold;
const willOpen = Boolean(!pending.canceled && !pending.opened && !isDrag);
const interaction = findMermaidNodeInteractionByTarget(interactions, pending.target);
logMermaidInteractionClickDebug(options, "pointerup", event, interaction, distance, isDrag, willOpen, pending.opened);
if (willOpen) {
event.preventDefault();
event.stopPropagation();
pending.opened = true;
lastPointerupOpen = { mermaidId: pending.target.mermaidId, at: event.timeStamp };
openMermaidNodeTarget(options, pending.target, event);
}
pendingOpen = null;
}, { signal: controller.signal });
documentEl.addEventListener("pointercancel", (event) => {
if (!pendingOpen || pendingOpen.pointerId !== event.pointerId) {
return;
}
pendingOpen.canceled = true;
pendingOpen = null;
}, { signal: controller.signal });
options.rootEl.addEventListener("pointermove", (event) => {
const interaction = getMermaidNodeInteractionFromEvent(event, interactions);
if (!interaction) {
clearGraphHoverState(hoverState, "blank-clear", {
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
}, options.rootEl, event, false, true);
return;
}
const isSameNode = hoverState.activeHoverNode === interaction.nodeEl;
const isSameLink = hoverState.activeHoverLinktext === interaction.target.linktext && hoverState.activeHoverSourcePath === interaction.target.sourcePath;
if (isSameNode && isSameLink) {
logGraphHoverStateDebug(
{ debugName: options.debugName, isDebugEnabled: options.isDebugEnabled },
hoverState,
"same-node-move",
event,
false,
false
);
return;
}
const action = hoverState.activeHoverNode ? "switch-node" : "enter";
const previousHoverNodeId = getGraphHoverNodeId(hoverState.activeHoverNode);
clearGraphHoverState(hoverState, action, {
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
}, options.rootEl, event, false);
const hoverLinkTarget = triggerMermaidNodeHoverPreview(
options,
source,
interaction.nodeEl,
interaction.target,
event,
{
activeHoverNodeId: getGraphHoverNodeId(interaction.nodeEl),
previousHoverNodeId,
hoverStateAction: action,
anchorVisible: true,
staleHoverSuppressed: false,
hoverLinkTriggered: true
}
);
setGraphHoverState(hoverState, interaction.nodeEl, interaction.target, hoverLinkTarget);
}, { signal: controller.signal });
options.rootEl.addEventListener("pointerleave", (event) => {
clearGraphHoverState(hoverState, "leave-node", {
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
}, options.rootEl, event, false);
}, { signal: controller.signal });
options.rootEl.addEventListener("click", (event) => {
const interaction = getMermaidNodeInteractionFromEvent(event, interactions);
const dragDistance = getMermaidNodeDragDistance(pointerStart, event);
const willOpen = Boolean(
interaction && isMermaidNodeClick(pointerStart, event, interaction.target.mermaidId, dragThreshold)
);
const alreadyOpenedByPointerup = Boolean(
interaction && lastPointerupOpen?.mermaidId === interaction.target.mermaidId && event.timeStamp - lastPointerupOpen.at < 1e3
);
logMermaidInteractionClickDebug(
options,
"click",
event,
interaction,
dragDistance,
!willOpen && dragDistance !== void 0 && dragDistance > dragThreshold,
willOpen && !alreadyOpenedByPointerup,
alreadyOpenedByPointerup || (pendingOpen?.opened ?? false)
);
if (!interaction || !willOpen || alreadyOpenedByPointerup) {
return;
}
event.preventDefault();
event.stopPropagation();
if (pendingOpen && pendingOpen.target === interaction.target) {
if (pendingOpen.opened) {
pendingOpen = null;
return;
}
pendingOpen.opened = true;
}
openMermaidNodeTarget(options, interaction.target, event);
pendingOpen = null;
}, { signal: controller.signal });
return () => {
controller.abort();
clearGraphHoverState(hoverState, "cleanup", {
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
}, options.rootEl, void 0, false);
};
}
function attachGraphElementHoverPreview(options) {
const fallback = options.rootEl ?? getElementHoverFallback(options.targetEl);
if (!fallback || !canShowGraphInteractionHover(options.target)) {
return () => void 0;
}
const controller = new AbortController();
const source = options.source ?? "model-weave";
const hoverState = createGraphHoverState();
options.targetEl.addEventListener("pointermove", (event) => {
const isSameNode = hoverState.activeHoverNode === options.targetEl;
const isSameLink = hoverState.activeHoverLinktext === options.target.linktext && hoverState.activeHoverSourcePath === options.target.sourcePath;
if (isSameNode && isSameLink) {
logGraphHoverStateDebug(
{ debugName: options.debugName, isDebugEnabled: options.isDebugEnabled },
hoverState,
"same-node-move",
event,
false,
false
);
return;
}
const action = hoverState.activeHoverNode ? "switch-node" : "enter";
const previousHoverNodeId = getGraphHoverNodeId(hoverState.activeHoverNode);
clearGraphHoverState(hoverState, action, {
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
}, fallback, event, false);
const hoverLinkTarget = triggerGraphInteractionHover(
options.app,
source,
resolveGraphHoverParent(options.targetEl, fallback, options.hoverParent),
options.targetEl,
options.target,
event,
{
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
},
{
activeHoverNodeId: getGraphHoverNodeId(options.targetEl),
previousHoverNodeId,
hoverStateAction: action,
anchorVisible: true,
staleHoverSuppressed: false,
hoverLinkTriggered: true
}
);
setGraphHoverState(hoverState, options.targetEl, options.target, hoverLinkTarget);
}, { signal: controller.signal });
options.targetEl.addEventListener("pointerleave", (event) => {
clearGraphHoverState(hoverState, "leave-node", {
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
}, fallback, event, false);
}, { signal: controller.signal });
return () => {
controller.abort();
clearGraphHoverState(hoverState, "cleanup", {
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
}, fallback, void 0, false);
};
}
function createGraphHoverState() {
return {
activeHoverNode: null,
activeHoverLinktext: "",
activeHoverSourcePath: "",
activeHoverTargetEl: null
};
}
function setGraphHoverState(state, node, target, hoverLinkTarget) {
state.activeHoverNode = node;
state.activeHoverLinktext = target.linktext;
state.activeHoverSourcePath = target.sourcePath;
state.activeHoverTargetEl = hoverLinkTarget?.targetEl ?? null;
}
function clearGraphHoverState(state, action, debug, rootEl, event, suppressSyntheticLeave, staleHoverSuppressed = false) {
const previousHoverNodeId = getGraphHoverNodeId(state.activeHoverNode);
const activeHoverTargetEl = state.activeHoverTargetEl;
const hadActiveNode = Boolean(state.activeHoverNode);
state.activeHoverNode = null;
state.activeHoverLinktext = "";
state.activeHoverSourcePath = "";
state.activeHoverTargetEl = null;
if (activeHoverTargetEl && !suppressSyntheticLeave) {
if (isGraphFallbackHoverCard(activeHoverTargetEl)) {
activeHoverTargetEl.remove();
} else {
dispatchGraphHoverTargetLeave(activeHoverTargetEl);
}
} else if (hadActiveNode && !suppressSyntheticLeave) {
dispatchGraphHoverTargetLeave(rootEl);
}
logGraphHoverStateDebug(
debug,
state,
action,
event,
staleHoverSuppressed,
false,
previousHoverNodeId
);
}
function logGraphHoverStateDebug(debug, state, action, event, staleHoverSuppressed, hoverLinkTriggered, previousHoverNodeId) {
if (debug?.isDebugEnabled?.() !== true) {
return;
}
const mouseEvent = event instanceof MouseEvent ? event : null;
console.debug("Model Weave graph hover state debug", {
debugName: debug.debugName,
activeHoverNodeId: getGraphHoverNodeId(state.activeHoverNode),
previousHoverNodeId,
hoverStateAction: action,
anchorVisible: Boolean(state.activeHoverTargetEl),
staleHoverSuppressed,
hoverLinkTriggered,
clientX: mouseEvent?.clientX,
clientY: mouseEvent?.clientY
});
}
function getGraphHoverNodeId(node) {
return node?.id || node?.getAttribute("data-id") || void 0;
}
function buildMermaidNodeInteractions(svg, targets, nodeSelector, findNodeElements) {
if (findNodeElements) {
return findNodeElements(svg, targets).map((match) => ({
nodeEl: match.element,
target: match.target
}));
}
return targets.map((target) => {
const nodeEl = findMermaidSvgNode(svg, target.mermaidId, nodeSelector);
return nodeEl ? { nodeEl, target } : null;
}).filter((interaction) => Boolean(interaction));
}
function findMermaidSvgNode(svg, mermaidId, nodeSelector) {
const nodes = Array.from(svg.querySelectorAll(nodeSelector));
return nodes.find((node) => node.id.includes(mermaidId)) ?? null;
}
function getMermaidNodeInteractionFromEvent(event, interactions) {
const eventTarget = event.target;
if (!(eventTarget instanceof Element)) {
return null;
}
return interactions.find(
(interaction) => interaction.nodeEl === eventTarget || interaction.nodeEl.contains(eventTarget)
) ?? null;
}
function findMermaidNodeInteractionByTarget(interactions, target) {
return interactions.find((interaction) => interaction.target === target) ?? null;
}
function getGraphInteractionNativeTooltipText(target, fallbackTitle) {
return target.nativeTooltip ?? fallbackTitle ?? target.label ?? target.linktext;
}
function setMermaidNodeTitle(nodeEl, target, formatTitle) {
const existingTitle = nodeEl.querySelector("title");
const titleText = getGraphInteractionNativeTooltipText(target, formatTitle?.(target));
if (!titleText) {
existingTitle?.remove();
return;
}
const doc = nodeEl.ownerDocument;
const title = existingTitle ?? doc.createElementNS("http://www.w3.org/2000/svg", "title");
title.textContent = titleText;
if (!title.parentElement) {
nodeEl.prepend(title);
}
}
function triggerMermaidNodeHoverPreview(options, source, targetEl, target, event, stateDebug) {
if (!canShowGraphInteractionHover(target)) {
return null;
}
const hoverParent = resolveGraphHoverParent(targetEl, options.rootEl, options.hoverParent);
return triggerGraphInteractionHover(
options.app,
source,
hoverParent,
targetEl,
target,
event,
{
debugName: options.debugName,
isDebugEnabled: options.isDebugEnabled
},
stateDebug
);
}
function canShowGraphInteractionHover(target) {
return Boolean(
target.previewLinktext && target.sourcePath || target.linktext && target.sourcePath && !target.hoverRows?.length || target.hoverRows?.length
);
}
function triggerGraphInteractionHover(app, source, hoverParent, targetEl, target, event, debug, stateDebug) {
const previewLinktext = getGraphInteractionPreviewLinktext(target);
if (previewLinktext) {
const previewTarget = { ...target, linktext: previewLinktext };
return triggerGraphInteractionHoverPreview(
app,
source,
hoverParent,
targetEl,
previewTarget,
event,
debug,
stateDebug
) ?? triggerGraphInteractionFallbackHover(hoverParent, target, event);
}
return triggerGraphInteractionFallbackHover(hoverParent, target, event);
}
function getGraphInteractionPreviewLinktext(target) {
const explicit = target.previewLinktext?.trim();
if (explicit) {
return explicit;
}
if (target.hoverRows?.length) {
return null;
}
const linktext = target.linktext?.trim();
return linktext && target.sourcePath ? linktext : null;
}
function triggerGraphInteractionFallbackHover(hoverParent, target, event) {
if (!target.hoverRows?.length) {
return null;
}
const card = createGraphFallbackHoverCard(hoverParent, target, event);
return { targetEl: card, reusableAnchorTargetUsed: false };
}
function createGraphFallbackHoverCard(hoverParent, target, event) {
const doc = hoverParent.ownerDocument;
const existing = hoverParent.querySelectorAll(".model-weave-graph-hover-card");
existing.forEach((element) => element.remove());
const card = doc.createElement("div");
card.className = "model-weave-graph-hover-card";
card.setAttribute("role", "tooltip");
const title = doc.createElement("div");
title.className = "model-weave-graph-hover-card-title";
title.textContent = target.hoverTitle ?? target.label ?? "Model Weave";
card.appendChild(title);
const rows = doc.createElement("dl");
rows.className = "model-weave-graph-hover-card-rows";
for (const row of target.hoverRows ?? []) {
const term = doc.createElement("dt");
term.textContent = row.label;
const description = doc.createElement("dd");
description.textContent = row.value?.trim() || "-";
rows.appendChild(term);
rows.appendChild(description);
}
card.appendChild(rows);
hoverParent.appendChild(card);
positionGraphFallbackHoverCard(card, hoverParent, event);
return card;
}
function positionGraphFallbackHoverCard(card, hoverParent, event) {
const view = hoverParent.ownerDocument.defaultView;
const viewportWidth = view?.innerWidth ?? event.clientX + 360;
const x = Math.min(Math.max(12, event.clientX + 14), Math.max(12, viewportWidth - 380));
const y = Math.max(12, event.clientY + 14);
card.style.left = `${x}px`;
card.style.top = `${y}px`;
}
function isGraphFallbackHoverCard(element) {
return element instanceof HTMLElement && element.classList.contains("model-weave-graph-hover-card");
}
function triggerGraphInteractionHoverPreview(app, source, hoverParent, targetEl, target, event, debug, stateDebug) {
if (!target.linktext || !target.sourcePath) {
return null;
}
const hoverLinkEvent = createHoverLinkEventWithSafeCoordinates(event, hoverParent);
const hoverLinkTarget = resolveGraphHoverLinkTarget(targetEl, hoverParent);
const debugContext = {
originalTargetEl: targetEl,
hoverLinkTargetEl: hoverLinkTarget.targetEl,
hoverParent,
target,
originalEvent: event,
hoverLinkEvent: hoverLinkEvent.event,
safeCoordinateApplied: hoverLinkEvent.safeCoordinateApplied,
originalClientY: event.clientY,
safeClientY: hoverLinkEvent.safeClientY,
reusableAnchorTargetUsed: hoverLinkTarget.reusableAnchorTargetUsed,
activeHoverNodeId: stateDebug?.activeHoverNodeId,
previousHoverNodeId: stateDebug?.previousHoverNodeId,
hoverStateAction: stateDebug?.hoverStateAction,
anchorVisible: stateDebug?.anchorVisible ?? Boolean(hoverLinkTarget.targetEl),
staleHoverSuppressed: stateDebug?.staleHoverSuppressed ?? false,
hoverLinkTriggered: stateDebug?.hoverLinkTriggered ?? true
};
logGraphInteractionHoverDebug(debug, debugContext, "before-trigger");
try {
app.workspace.trigger("hover-link", {
event: hoverLinkEvent.event,
source,
hoverParent,
targetEl: hoverLinkTarget.targetEl,
linktext: target.linktext,
sourcePath: target.sourcePath
});
logGraphInteractionHoverDebug(debug, debugContext, "after-trigger");
scheduleDelayedGraphInteractionHoverDebug(debug, debugContext);
return hoverLinkTarget;
} catch {
logGraphInteractionHoverDebug(debug, debugContext, "trigger-error");
return null;
}
}
function resolveGraphHoverLinkTargetElement(targetEl, hoverParent) {
return targetEl.closest(".model-weave-graph-canvas") ?? targetEl.closest(".model-weave-graph-viewport") ?? targetEl.closest(".model-weave-viewer-root") ?? hoverParent;
}
function resolveGraphHoverLinkTarget(targetEl, hoverParent) {
return {
targetEl: resolveGraphHoverLinkTargetElement(targetEl, hoverParent),
reusableAnchorTargetUsed: false
};
}
function resolveGraphHoverParent(targetEl, fallback, hoverParent = void 0) {
const explicitHoverParent = typeof hoverParent === "function" ? hoverParent(targetEl, fallback) : hoverParent;
if (explicitHoverParent) {
return explicitHoverParent;
}
const viewOnlyStage = targetEl.closest(".model-weave-view-only-stage");
if (viewOnlyStage) {
return viewOnlyStage;
}
const viewerRoot = targetEl.closest(".model-weave-viewer-root");
if (viewerRoot) {
return viewerRoot;
}
const workspaceLeafContent = targetEl.closest(".workspace-leaf-content");
if (workspaceLeafContent) {
return workspaceLeafContent;
}
return fallback;
}
function getElementHoverFallback(targetEl) {
return targetEl instanceof HTMLElement ? targetEl : targetEl.ownerSVGElement?.parentElement ?? null;
}
function isMermaidNodeClick(pointerStart, event, mermaidId, dragThreshold) {
if (!pointerStart) {
return true;
}
if (mermaidId && pointerStart.mermaidId !== mermaidId) {
return false;
}
const distance = getMermaidNodeDragDistance(pointerStart, event);
return distance === void 0 || distance <= dragThreshold;
}
function getMermaidNodeDragDistance(pointerStart, event) {
if (!pointerStart) {
return void 0;
}
return Math.hypot(event.clientX - pointerStart.x, event.clientY - pointerStart.y);
}
function getPendingMermaidOpenDistance(pendingOpen, event) {
return Math.hypot(event.clientX - pendingOpen.startX, event.clientY - pendingOpen.startY);
}
function openMermaidNodeTarget(options, target, event) {
logMermaidInteractionOpenDebug(options, target);
if (options.openLinkText) {
void options.openLinkText(target, event);
return;
}
void options.app.workspace.openLinkText(
target.linktext,
target.sourcePath,
event.ctrlKey || event.metaKey
);
}
function logMermaidInteractionClickDebug(options, phase, event, interaction, dragDistance, isDrag, willOpen, opened = false) {
if (options.isDebugEnabled?.() !== true) {
return;
}
const eventTarget = event.target instanceof Element ? event.target : null;
const currentTarget = event.currentTarget instanceof Element ? event.currentTarget : null;
const target = interaction?.target;
console.debug("Model Weave mermaid interaction click debug", {
debugName: options.debugName,
phase,
eventTargetTag: eventTarget?.tagName,
eventTargetId: eventTarget?.id,
currentTargetTag: currentTarget?.tagName,
currentTargetId: currentTarget?.id,
resolvedTarget: target ? {
mermaidId: target.mermaidId,
linktext: target.linktext,
sourcePath: target.sourcePath,
filePath: target.filePath,
kind: target.kind,
targetType: target.targetType
} : null,
dragDistance,
isDrag,
willOpen,
opened
});
}
function logMermaidInteractionOpenDebug(options, target) {
if (options.isDebugEnabled?.() !== true) {
return;
}
console.debug("Model Weave mermaid interaction open link", {
debugName: options.debugName,
linktext: target.linktext,
sourcePath: target.sourcePath,
filePath: target.filePath
});
}
function logGraphInteractionHoverDebug(debug, context, phase) {
if (debug?.isDebugEnabled?.() !== true) {
return;
}
const doc = context.originalTargetEl.ownerDocument;
const shouldInspectPopovers = phase === "after-trigger" || phase === "after-300ms" || phase === "after-1000ms";
const hoverPopovers = Array.from(doc.querySelectorAll(HOVER_POPOVER_SELECTOR));
const focusOverlay = doc.querySelector(FOCUS_MODE_OVERLAY_SELECTOR);
console.debug("Model Weave graph hover preview debug", {
debugName: debug.debugName,
phase,
linktext: context.target.linktext,
sourcePath: context.target.sourcePath,
originalTargetEl: describeElement(context.originalTargetEl),
hoverLinkTargetEl: describeElement(context.hoverLinkTargetEl),
hoverParent: describeElement(context.hoverParent),
activeHoverNodeId: context.activeHoverNodeId,
previousHoverNodeId: context.previousHoverNodeId,
hoverStateAction: context.hoverStateAction,
anchorVisible: context.anchorVisible,
staleHoverSuppressed: context.staleHoverSuppressed,
hoverLinkTriggered: context.hoverLinkTriggered,
reusableAnchorTargetUsed: context.reusableAnchorTargetUsed,
clientX: context.hoverLinkEvent.clientX,
clientY: context.hoverLinkEvent.clientY,
originalClientY: context.originalClientY,
safeClientY: context.safeClientY,
safeCoordinateApplied: context.safeCoordinateApplied,
targetRect: toDebugRect(context.originalTargetEl.getBoundingClientRect()),
hoverLinkTargetRect: toDebugRect(context.hoverLinkTargetEl.getBoundingClientRect()),
hoverParentRect: toDebugRect(context.hoverParent.getBoundingClientRect()),
focusModeActive: Boolean(doc.body.classList.contains("model-weave-focus-mode-active")),
viewOnlyModeActive: Boolean(context.originalTargetEl.closest(".model-weave-viewer-view-only")),
hoverPopoverSelector: HOVER_POPOVER_SELECTOR,
hoverPopoverCount: hoverPopovers.length,
hoverPopoverDetails: shouldInspectPopovers ? hoverPopovers.map((element) => describeDebugElementDetails(element, HOVER_POPOVER_SELECTOR)) : void 0,
focusOverlaySelector: FOCUS_MODE_OVERLAY_SELECTOR,
focusOverlayDetails: shouldInspectPopovers && focusOverlay ? describeDebugElementDetails(focusOverlay, FOCUS_MODE_OVERLAY_SELECTOR) : null
});
}
function scheduleDelayedGraphInteractionHoverDebug(debug, context) {
if (debug?.isDebugEnabled?.() !== true) {
return;
}
const view = context.originalTargetEl.ownerDocument.defaultView;
view?.setTimeout(() => {
logGraphInteractionHoverDebug(debug, context, "after-300ms");
}, 300);
view?.setTimeout(() => {
logGraphInteractionHoverDebug(debug, context, "after-1000ms");
}, 1e3);
}
function dispatchGraphHoverTargetLeave(targetEl) {
const view = targetEl.ownerDocument.defaultView;
if (!view) {
return;
}
targetEl.dispatchEvent(new view.MouseEvent("mouseout", { bubbles: true }));
targetEl.dispatchEvent(new view.MouseEvent("mouseleave", { bubbles: false }));
}
function createHoverLinkEventWithSafeCoordinates(event, hoverParent) {
const hoverParentRect = hoverParent.getBoundingClientRect();
const safeTop = Math.max(hoverParentRect.top + 160, 160);
const safeClientY = Math.max(event.clientY, safeTop);
if (safeClientY === event.clientY) {
return {
event,
safeCoordinateApplied: false,
safeClientY
};
}
const hoverLinkEvent = Object.create(event);
Object.defineProperty(hoverLinkEvent, "clientY", {
configurable: true,
enumerable: true,
value: safeClientY
});
return {
event: hoverLinkEvent,
safeCoordinateApplied: true,
safeClientY
};
}
function describeElement(element) {
const className = typeof element.className === "string" ? element.className : element.getAttribute("class") ?? void 0;
return {
tag: element.tagName,
id: element.id || void 0,
className: className || void 0
};
}
function describeDebugElementDetails(element, selectorMatched) {
const view = element.ownerDocument.defaultView;
const style = view?.getComputedStyle(element);
return {
selectorMatched,
className: element.className || void 0,
boundingClientRect: toDebugRect(element.getBoundingClientRect()),
computedZIndex: style?.zIndex ?? "",
computedPosition: style?.position ?? "",
computedDisplay: style?.display ?? "",
computedVisibility: style?.visibility ?? "",
computedOpacity: style?.opacity ?? "",
computedPointerEvents: style?.pointerEvents ?? "",
computedTransform: style?.transform ?? "",
computedOverflow: style?.overflow ?? ""
};
}
function toDebugRect(rect) {
return {
left: Math.round(rect.left),
top: Math.round(rect.top),
width: Math.round(rect.width),
height: Math.round(rect.height),
right: Math.round(rect.right),
bottom: Math.round(rect.bottom)
};
}
// src/renderers/class-renderer.ts
var SVG_NS = "http://www.w3.org/2000/svg";
var NODE_WIDTH = 300;
var HEADER_HEIGHT = 38;
var SECTION_TITLE_HEIGHT = 24;
var ROW_HEIGHT = 20;
var NODE_PADDING = 12;
var LIST_INDENT_WIDTH = 18;
var ESTIMATED_ROW_CHAR_WIDTH = 7;
var COLUMN_GAP = 96;
var ROW_GAP = 92;
var CANVAS_PADDING = 48;
var CLASS_CARD_DECORATION_BOUNDS_PADDING = 10;
var CLASS_ROW_AVAILABLE_CHARS = Math.max(
1,
Math.floor((NODE_WIDTH - NODE_PADDING * 2 - LIST_INDENT_WIDTH) / ESTIMATED_ROW_CHAR_WIDTH)
);
var DEFAULT_ATTRIBUTE_LIMIT = 5;
var DEFAULT_METHOD_LIMIT = 5;
var MIN_ZOOM2 = 0.45;
var MAX_ZOOM2 = 2.4;
var INITIAL_ZOOM2 = 1;
var DIAGRAM_LABEL_BG = "#ffffff";
var DIAGRAM_LABEL_BORDER = "#e5e7eb";
var DIAGRAM_LABEL_TEXT = "#111827";
var DIAGRAM_EDGE = "#374151";
function renderClassDiagram(diagram, options) {
const root = activeDocument.createElement("section");
root.addClass("model-weave-diagram-shell");
if (!options?.hideTitle) {
const title = activeDocument.createElement("h2");
title.textContent = `${diagram.diagram.name} (class)`;
title.addClass("model-weave-diagram-title");
root.appendChild(title);
}
const layout = createLayout(
diagram.nodes,
diagram.edges
);
const sceneBounds = createSceneBounds(diagram.edges, layout.byId);
const canvas = activeDocument.createElement("div");
canvas.addClass("model-weave-diagram-canvas");
if (!options?.forExport) {
canvas.addClass("model-weave-diagram-canvas-interactive");
}
const toolbar = options?.forExport ? null : createZoomToolbar("Ctrl/Meta + wheel: zoom / Drag background: pan", {
onExportPng: options?.onExportPng,
onExportAndOpenPng: options?.onExportAndOpenPng,
exportPngLabel: options?.exportPngLabel,
exportPngTitle: options?.exportPngTitle,
exportAndOpenPngLabel: options?.exportAndOpenPngLabel,
exportAndOpenPngTitle: options?.exportAndOpenPngTitle
});
if (toolbar) {
root.appendChild(toolbar.root);
}
const viewport = activeDocument.createElement("div");
viewport.addClass("model-weave-diagram-viewport");
const surface = activeDocument.createElement("div");
surface.addClass("model-weave-diagram-surface");
surface.dataset.modelWeaveExportSurface = "true";
surface.dataset.modelWeaveSceneWidth = `${sceneBounds.width}`;
surface.dataset.modelWeaveSceneHeight = `${sceneBounds.height}`;
surface.setCssProps({
"--mw-scene-width": `${sceneBounds.width}px`,
"--mw-scene-height": `${sceneBounds.height}px`
});
const svg = createSvgSurface(sceneBounds.width, sceneBounds.height);
svg.appendChild(createMarkerDefinitions());
for (const edge of diagram.edges) {
const edgeGroup = renderEdge(edge, layout.byId);
if (edgeGroup) {
svg.appendChild(edgeGroup);
}
}
surface.appendChild(svg);
for (const box of layout.nodes) {
surface.appendChild(createNodeBox(box, options));
}
viewport.appendChild(surface);
canvas.appendChild(viewport);
root.appendChild(canvas);
if (toolbar) {
attachGraphViewportInteractions(canvas, surface, toolbar, sceneBounds, {
minZoom: MIN_ZOOM2,
maxZoom: MAX_ZOOM2,
initialZoom: INITIAL_ZOOM2,
nodeSelector: ".model-weave-node",
fitVerticalAlign: options?.fitVerticalAlign,
fitContentBounds: createFitContentBounds(layout.nodes),
viewportState: options?.viewportState,
onViewportStateChange: options?.onViewportStateChange
});
}
if (!options?.hideDetails) {
root.appendChild(createConnectionsTable(diagram, options?.classDetailLabels));
}
return root;
}
function createLayout(nodes, edges) {
return buildGraphLayout(nodes, edges, {
getWidth: () => NODE_WIDTH,
getHeight: (node) => measureNodeHeight(node.object),
canvasPadding: CANVAS_PADDING,
columnGap: COLUMN_GAP,
rowGap: ROW_GAP,
maxColumns: 4
});
}
function createSceneBounds(edges, layoutById) {
const nodeBounds = Object.values(layoutById).map((layout) => ({
x: layout.x,
y: layout.y,
width: layout.width,
height: layout.height
}));
const labelBounds = estimateEdgeLabelBounds(
edges,
layoutById,
getMinimalEdgeLabel
);
return computeSceneBounds(
nodeBounds,
labelBounds,
CANVAS_PADDING + CLASS_CARD_DECORATION_BOUNDS_PADDING
);
}
function createFitContentBounds(nodes) {
if (nodes.length === 0) {
return void 0;
}
return {
top: Math.min(...nodes.map((node) => node.y)) - CLASS_CARD_DECORATION_BOUNDS_PADDING,
bottom: Math.max(...nodes.map((node) => node.y + node.height)) + CLASS_CARD_DECORATION_BOUNDS_PADDING
};
}
function measureNodeHeight(object) {
if (!object || object.fileType !== "object") {
return HEADER_HEIGHT + NODE_PADDING * 2 + ROW_HEIGHT;
}
const attributeRows = estimateVisibleRows(getVisibleAttributes(object));
const methodRows = estimateVisibleRows(getVisibleMethods(object));
return HEADER_HEIGHT + SECTION_TITLE_HEIGHT + attributeRows * ROW_HEIGHT + SECTION_TITLE_HEIGHT + methodRows * ROW_HEIGHT + NODE_PADDING * 2;
}
function estimateVisibleRows(items) {
if (items.length === 0) {
return 1;
}
return items.reduce(
(sum, item) => sum + estimateWrappedLineCount(item, CLASS_ROW_AVAILABLE_CHARS),
0
);
}
function estimateWrappedLineCount(text, availableCharsPerLine) {
const normalizedText = text.trim();
if (!normalizedText) {
return 1;
}
return Math.max(1, Math.ceil(normalizedText.length / availableCharsPerLine));
}
function createSvgSurface(width, height) {
const svg = activeDocument.createElementNS(SVG_NS, "svg");
svg.setAttribute("width", String(width));
svg.setAttribute("height", String(height));
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
svg.setAttribute("class", "model-weave-diagram-svg");
return svg;
}
function createMarkerDefinitions() {
const defs = activeDocument.createElementNS(SVG_NS, "defs");
defs.appendChild(
createTriangleMarker("mdspec-arrow-solid", DIAGRAM_EDGE, DIAGRAM_EDGE)
);
defs.appendChild(createTriangleMarker("mdspec-arrow-open", "none", DIAGRAM_EDGE));
defs.appendChild(
createDiamondMarker("mdspec-diamond-open", "none", DIAGRAM_EDGE)
);
defs.appendChild(
createDiamondMarker(
"mdspec-diamond-solid",
DIAGRAM_EDGE,
DIAGRAM_EDGE
)
);
return defs;
}
function createTriangleMarker(id, fill, stroke) {
const marker = activeDocument.createElementNS(SVG_NS, "marker");
marker.setAttribute("id", id);
marker.setAttribute("markerWidth", "12");
marker.setAttribute("markerHeight", "12");
marker.setAttribute("refX", "10");
marker.setAttribute("refY", "6");
marker.setAttribute("orient", "auto");
marker.setAttribute("markerUnits", "strokeWidth");
const path2 = activeDocument.createElementNS(SVG_NS, "path");
path2.setAttribute("d", "M 0 0 L 10 6 L 0 12 z");
path2.setAttribute("fill", fill);
path2.setAttribute("stroke", stroke);
path2.setAttribute("stroke-width", "1.2");
marker.appendChild(path2);
return marker;
}
function createDiamondMarker(id, fill, stroke) {
const marker = activeDocument.createElementNS(SVG_NS, "marker");
marker.setAttribute("id", id);
marker.setAttribute("markerWidth", "14");
marker.setAttribute("markerHeight", "14");
marker.setAttribute("refX", "12");
marker.setAttribute("refY", "7");
marker.setAttribute("orient", "auto");
marker.setAttribute("markerUnits", "strokeWidth");
const path2 = activeDocument.createElementNS(SVG_NS, "path");
path2.setAttribute("d", "M 0 7 L 4 0 L 12 7 L 4 14 z");
path2.setAttribute("fill", fill);
path2.setAttribute("stroke", stroke);
path2.setAttribute("stroke-width", "1.2");
marker.appendChild(path2);
return marker;
}
function renderEdge(edge, layoutById) {
const source = layoutById[edge.source];
const target = layoutById[edge.target];
if (!source || !target) {
return null;
}
const group = activeDocument.createElementNS(SVG_NS, "g");
const { startX, startY, endX, endY, midX, midY } = getConnectionPoints(
source,
target
);
const line = activeDocument.createElementNS(SVG_NS, "line");
line.setAttribute("x1", String(startX));
line.setAttribute("y1", String(startY));
line.setAttribute("x2", String(endX));
line.setAttribute("y2", String(endY));
line.setAttribute("stroke", DIAGRAM_EDGE);
line.setAttribute("stroke-width", "2");
line.setAttribute("stroke-dasharray", getDashPattern(edge.kind));
const markers = getMarkerAttributes(edge.kind);
if (markers.start) {
line.setAttribute("marker-start", markers.start);
}
if (markers.end) {
line.setAttribute("marker-end", markers.end);
}
group.appendChild(line);
const edgeLabel = getMinimalEdgeLabel(edge);
if (edgeLabel) {
group.appendChild(createEdgeBadge(midX, midY - 8, edgeLabel));
}
return group;
}
function getMinimalEdgeLabel(edge) {
const internalEdge = classDiagramEdgeToInternalEdge(edge);
switch (internalEdge.kind) {
case "inheritance":
return "inheritance";
case "implementation":
return "implementation";
case "dependency":
return "dependency";
case "composition":
return "composition";
case "aggregation":
return "aggregation";
case "association":
return "association";
default:
return internalEdge.kind ?? null;
}
}
function createEdgeBadge(x, y, value) {
const group = activeDocument.createElementNS(SVG_NS, "g");
const width = Math.max(52, value.length * 8 + 12);
const height = 20;
const rect = activeDocument.createElementNS(SVG_NS, "rect");
rect.setAttribute("x", String(x - width / 2));
rect.setAttribute("y", String(y - height / 2));
rect.setAttribute("width", String(width));
rect.setAttribute("height", String(height));
rect.setAttribute("rx", "10");
rect.setAttribute("fill", DIAGRAM_LABEL_BG);
rect.setAttribute("stroke", DIAGRAM_LABEL_BORDER);
group.appendChild(rect);
const text = activeDocument.createElementNS(SVG_NS, "text");
text.setAttribute("x", String(x));
text.setAttribute("y", String(y + 4));
text.setAttribute("text-anchor", "middle");
text.setAttribute("font-size", "11px");
text.setAttribute("font-weight", "600");
text.setAttribute("fill", DIAGRAM_LABEL_TEXT);
text.textContent = value;
group.appendChild(text);
return group;
}
function getDashPattern(kind) {
switch (kind) {
case "dependency":
case "implementation":
return "8 6";
default:
return "0";
}
}
function getMarkerAttributes(kind) {
switch (kind) {
case "inheritance":
return { end: "url(#mdspec-arrow-open)" };
case "implementation":
return { end: "url(#mdspec-arrow-open)" };
case "dependency":
return { end: "url(#mdspec-arrow-solid)" };
case "aggregation":
return { start: "url(#mdspec-diamond-open)" };
case "composition":
return { start: "url(#mdspec-diamond-solid)" };
case "association":
case "reference":
case "flow":
default:
return { end: "url(#mdspec-arrow-solid)" };
}
}
function createNodeBox(layout, options) {
const box = activeDocument.createElement("article");
box.addClass("model-weave-node");
box.addClass(
layout.node.object?.fileType === "object" && layout.node.object.kind === "interface" ? "model-weave-node-interface" : "model-weave-node-class"
);
box.setCssProps({
"--mw-node-x": `${layout.x}px`,
"--mw-node-y": `${layout.y}px`,
"--mw-node-width": `${layout.width}px`,
"--mw-node-height": `${layout.height}px`
});
if (!layout.node.object) {
box.appendChild(createFallbackNode(layout.node.label ?? layout.node.ref ?? layout.node.id));
return box;
}
attachClassNodeHoverPreview(box, layout, options);
if (options?.onOpenObject) {
box.addClass("model-weave-node-clickable");
box.setAttribute("role", "button");
box.setAttribute("tabindex", "0");
box.title = `Open ${layout.node.label ?? (layout.node.object.fileType === "object" ? layout.node.object.name : layout.node.object.logicalName)}`;
box.addEventListener("click", (event) => {
if (event.defaultPrevented) {
return;
}
options.onOpenObject?.(layout.node.id, {
openInNewLeaf: event.ctrlKey || event.metaKey
});
});
box.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
options.onOpenObject?.(layout.node.id, { openInNewLeaf: false });
}
});
box.addEventListener("pointerdown", (event) => {
event.stopPropagation();
});
}
const object = layout.node.object;
if (object.fileType !== "object") {
box.appendChild(createFallbackNode(layout.node.label ?? object.logicalName));
return box;
}
const header = activeDocument.createElement("header");
header.addClass("model-weave-node-header");
header.addClass(getHeaderModifierClass(object.kind));
const kind = activeDocument.createElement("div");
kind.addClass("model-weave-node-kind");
kind.textContent = object.kind;
const title = activeDocument.createElement("div");
title.addClass("model-weave-node-title");
title.textContent = layout.node.label ?? object.name;
header.append(kind, title);
box.appendChild(header);
box.appendChild(createNodeSection("Attributes", getVisibleAttributes(object)));
box.appendChild(createNodeSection("Methods", getVisibleMethods(object)));
return box;
}
function attachClassNodeHoverPreview(box, layout, options) {
if (!options?.app || options.forExport) {
return;
}
const target = createClassNodeInteractionTarget(layout, options.interactionSourcePath);
if (!target) {
return;
}
attachGraphElementHoverPreview({
app: options.app,
targetEl: box,
target,
source: "model-weave"
});
}
function createClassNodeInteractionTarget(layout, sourcePath) {
const object = layout.node.object;
if (!object?.path) {
return null;
}
const label = object.fileType === "er-entity" ? object.logicalName || object.physicalName || layout.node.label || layout.node.id : object.name || layout.node.label || layout.node.id;
const resolvedSourcePath = sourcePath ?? object.path;
const targetType = object.fileType === "er-entity" ? "er_entity" : "class";
const kind = object.path === resolvedSourcePath ? targetType === "er_entity" ? "er-current" : "class-current" : targetType === "er_entity" ? "er-reference" : "class-reference";
return {
mermaidId: layout.node.id,
linktext: object.path,
sourcePath: resolvedSourcePath,
label,
kind,
targetType,
filePath: object.path,
modelId: layout.node.id,
modelType: object.fileType
};
}
function getVisibleAttributes(object) {
const visible = object.attributes.slice(0, DEFAULT_ATTRIBUTE_LIMIT).map((attribute) => {
const detail = attribute.type ? `: ${attribute.type}` : "";
return `${attribute.name}${detail}`;
});
if (object.attributes.length > DEFAULT_ATTRIBUTE_LIMIT) {
visible.push("...");
}
return visible;
}
function getVisibleMethods(object) {
const visible = object.methods.slice(0, DEFAULT_METHOD_LIMIT).map((method) => {
const parameters = method.parameters.map(
(parameter) => `${parameter.name}${parameter.type ? `: ${parameter.type}` : ""}`
).join(", ");
const returnType = method.returnType ? ` ${method.returnType}` : "";
return `${method.name}(${parameters})${returnType}`;
});
if (object.methods.length > DEFAULT_METHOD_LIMIT) {
visible.push("...");
}
return visible;
}
function createNodeSection(title, items) {
const section = activeDocument.createElement("section");
section.addClass("model-weave-node-section");
const heading = activeDocument.createElement("div");
heading.addClass("model-weave-node-section-heading");
heading.textContent = title;
section.appendChild(heading);
if (items.length === 0) {
const empty = activeDocument.createElement("div");
empty.addClass("model-weave-node-empty");
empty.textContent = "None";
section.appendChild(empty);
return section;
}
const list = activeDocument.createElement("ul");
list.addClass("model-weave-node-list");
for (const item of items) {
const entry = activeDocument.createElement("li");
entry.textContent = item;
list.appendChild(entry);
}
section.appendChild(list);
return section;
}
function getHeaderModifierClass(kind) {
switch (kind) {
case "interface":
return "model-weave-node-header-interface";
case "enum":
return "model-weave-node-header-enum";
case "component":
return "model-weave-node-header-component";
case "entity":
return "model-weave-node-header-entity";
case "class":
default:
return "model-weave-node-header-class";
}
}
function createConnectionsTable(diagram, labels) {
const section = activeDocument.createElement("details");
section.addClass("model-weave-diagram-details");
section.open = false;
const summary = activeDocument.createElement("summary");
summary.textContent = `${labels?.displayedRelations ?? "Displayed relations"} (${diagram.edges.length})`;
summary.addClass("model-weave-diagram-details-summary");
section.appendChild(summary);
if (diagram.edges.length === 0) {
const empty = activeDocument.createElement("p");
empty.textContent = labels?.noRelationsUsed ?? "No relations are currently used for rendering.";
empty.addClass("model-weave-diagram-details-empty");
section.appendChild(empty);
return section;
}
const list = activeDocument.createElement("ul");
list.addClass("model-weave-diagram-details-list");
const sortedEdges = [...diagram.edges].sort(compareClassEdges);
for (const edge of sortedEdges) {
const internalEdge = classDiagramEdgeToInternalEdge(edge);
const details = buildEdgeDetails(internalEdge);
const item = activeDocument.createElement("li");
item.addClass("model-weave-diagram-details-item");
item.textContent = `${internalEdge.id || "-"} / ${internalEdge.sourceClass} -> ${internalEdge.targetClass} / ${internalEdge.kind || "-"} / ${internalEdge.label || "-"}${details ? ` / ${details}` : ""}${internalEdge.notes ? ` / ${internalEdge.notes}` : ""}`;
list.appendChild(item);
}
section.appendChild(list);
return section;
}
function buildEdgeDetails(edge) {
const parts = [];
if (edge.fromMultiplicity) {
parts.push(`from: ${edge.fromMultiplicity}`);
}
if (edge.toMultiplicity) {
parts.push(`to: ${edge.toMultiplicity}`);
}
return parts.join(" / ");
}
function createFallbackNode(id) {
const box = activeDocument.createElement("div");
box.addClass("model-weave-node-empty");
box.textContent = `Unresolved object: ${id}`;
return box;
}
function compareClassEdges(left, right) {
const leftEdge = classDiagramEdgeToInternalEdge(left);
const rightEdge = classDiagramEdgeToInternalEdge(right);
const sourceCompare = leftEdge.sourceClass.localeCompare(rightEdge.sourceClass);
if (sourceCompare !== 0) {
return sourceCompare;
}
const targetCompare = leftEdge.targetClass.localeCompare(rightEdge.targetClass);
if (targetCompare !== 0) {
return targetCompare;
}
return (leftEdge.id || "").localeCompare(rightEdge.id || "");
}
// src/renderers/er-shared.ts
var SVG_NS2 = "http://www.w3.org/2000/svg";
var DEFAULT_COLUMN_LIMIT = 5;
var ER_LABEL_BG = "#ffffff";
var ER_LABEL_BORDER = "#e5e7eb";
var ER_LABEL_TEXT = "#111827";
function getVisibleErColumns(columns, options) {
const highlighted = new Set(
(options?.highlightedColumns ?? []).map((value) => value.trim()).filter(Boolean)
);
const limit = options?.limit ?? DEFAULT_COLUMN_LIMIT;
const prioritized = [...columns].sort((left, right) => {
const leftScore = getColumnPriority(left, highlighted);
const rightScore = getColumnPriority(right, highlighted);
if (leftScore !== rightScore) {
return rightScore - leftScore;
}
return columns.indexOf(left) - columns.indexOf(right);
});
const visible = prioritized.slice(0, limit).map(formatErColumnLabel);
if (columns.length > limit) {
visible.push("...");
}
return visible;
}
function createErCardinalityBadge(x, y, value) {
const group = activeDocument.createElementNS(SVG_NS2, "g");
const width = Math.max(34, value.length * 8 + 12);
const height = 20;
const rect = activeDocument.createElementNS(SVG_NS2, "rect");
rect.setAttribute("x", String(x - width / 2));
rect.setAttribute("y", String(y - height / 2));
rect.setAttribute("width", String(width));
rect.setAttribute("height", String(height));
rect.setAttribute("rx", "10");
rect.setAttribute("fill", ER_LABEL_BG);
rect.setAttribute("stroke", ER_LABEL_BORDER);
group.appendChild(rect);
const text = activeDocument.createElementNS(SVG_NS2, "text");
text.setAttribute("x", String(x));
text.setAttribute("y", String(y + 4));
text.setAttribute("text-anchor", "middle");
text.setAttribute("font-size", "11px");
text.setAttribute("font-weight", "600");
text.setAttribute("fill", ER_LABEL_TEXT);
text.textContent = value;
group.appendChild(text);
return group;
}
function formatErColumnLabel(column) {
const parts = [`${column.logicalName} / ${column.physicalName}`, `: ${column.dataType}`];
if (column.pk) {
parts.push(" [PK]");
}
return parts.join("");
}
function getColumnPriority(column, highlighted) {
if (highlighted.has(column.physicalName) || highlighted.has(column.logicalName)) {
return column.pk ? 5 : 4;
}
if (column.pk) {
return 3;
}
const name = `${column.logicalName} ${column.physicalName}`.toLowerCase();
if (name.includes("id") || name.includes("_cd") || name.includes("code")) {
return 2;
}
return 1;
}
// src/renderers/er-renderer.ts
var SVG_NS3 = "http://www.w3.org/2000/svg";
var NODE_WIDTH2 = 280;
var HEADER_HEIGHT2 = 40;
var SECTION_TITLE_HEIGHT2 = 24;
var ROW_HEIGHT2 = 20;
var NODE_PADDING2 = 12;
var COLUMN_GAP2 = 96;
var ROW_GAP2 = 92;
var CANVAS_PADDING2 = 48;
var MIN_ZOOM3 = 0.45;
var MAX_ZOOM3 = 2.4;
var INITIAL_ZOOM3 = 1;
var DIAGRAM_EDGE2 = "#374151";
var ER_EDGE_STROKE_WIDTH = 2;
var ER_NODE_BORDER_WIDTH = 1;
var ER_ARROW_MARKER_WIDTH = 14;
var ER_ARROW_MARKER_HEIGHT = 14;
var ER_ARROW_TIP_X = 12;
var ER_ARROW_TIP_Y = 7;
var ER_ARROW_EXTRA_PADDING = 6;
var ER_DIAMOND_MARKER_WIDTH = 14;
var ER_DIAMOND_MARKER_HEIGHT = 14;
var ER_DIAMOND_TIP_X = 12;
var ER_DIAMOND_TIP_Y = 7;
var ER_DIAMOND_EXTRA_PADDING = 4;
var ER_MIN_EDGE_VISIBLE_LENGTH = 14;
function renderErDiagram(diagram, options) {
const root = activeDocument.createElement("section");
root.addClass("model-weave-diagram-shell");
if (!options?.hideTitle) {
const title = activeDocument.createElement("h2");
title.textContent = `${diagram.diagram.name} (ER)`;
title.addClass("model-weave-diagram-title");
root.appendChild(title);
}
const layout = createLayout2(
diagram.nodes,
diagram.edges
);
const sceneBounds = createSceneBounds2(diagram.edges, layout.byId);
const canvas = activeDocument.createElement("div");
canvas.addClass("model-weave-diagram-canvas");
if (!options?.forExport) {
canvas.addClass("model-weave-diagram-canvas-interactive");
}
const toolbar = options?.forExport ? null : createZoomToolbar("Ctrl/Meta + wheel: zoom / Drag background: pan", {
onExportPng: options?.onExportPng,
onExportAndOpenPng: options?.onExportAndOpenPng,
exportPngLabel: options?.exportPngLabel,
exportPngTitle: options?.exportPngTitle,
exportAndOpenPngLabel: options?.exportAndOpenPngLabel,
exportAndOpenPngTitle: options?.exportAndOpenPngTitle
});
if (toolbar) {
root.appendChild(toolbar.root);
}
const viewport = activeDocument.createElement("div");
viewport.addClass("model-weave-diagram-viewport");
const surface = activeDocument.createElement("div");
surface.addClass("model-weave-diagram-surface");
surface.dataset.modelWeaveExportSurface = "true";
surface.dataset.modelWeaveSceneWidth = `${sceneBounds.width}`;
surface.dataset.modelWeaveSceneHeight = `${sceneBounds.height}`;
surface.setCssProps({
"--mw-scene-width": `${sceneBounds.width}px`,
"--mw-scene-height": `${sceneBounds.height}px`
});
const svg = createSvgSurface2(sceneBounds.width, sceneBounds.height);
svg.appendChild(createMarkerDefinitions2());
for (const edge of diagram.edges) {
const edgeGroup = renderEdge2(edge, layout.byId);
if (edgeGroup) {
svg.appendChild(edgeGroup);
}
}
surface.appendChild(svg);
for (const box of layout.nodes) {
surface.appendChild(createEntityBox(box, options));
}
viewport.appendChild(surface);
canvas.appendChild(viewport);
root.appendChild(canvas);
if (toolbar) {
attachGraphViewportInteractions(canvas, surface, toolbar, sceneBounds, {
minZoom: MIN_ZOOM3,
maxZoom: MAX_ZOOM3,
initialZoom: INITIAL_ZOOM3,
nodeSelector: ".model-weave-node",
fitVerticalAlign: options?.fitVerticalAlign,
fitContentBounds: createFitContentBounds2(layout.nodes),
viewportState: options?.viewportState,
onViewportStateChange: options?.onViewportStateChange
});
}
if (!options?.hideDetails) {
root.appendChild(createRelationTable(diagram));
}
return root;
}
function createLayout2(nodes, edges) {
return buildGraphLayout(nodes, edges, {
getWidth: () => NODE_WIDTH2,
getHeight: (node) => measureNodeHeight2(node.object),
canvasPadding: CANVAS_PADDING2,
columnGap: COLUMN_GAP2,
rowGap: ROW_GAP2,
maxColumns: 4
});
}
function createSceneBounds2(edges, layoutById) {
const nodeBounds = Object.values(layoutById).map((layout) => ({
x: layout.x,
y: layout.y,
width: layout.width,
height: layout.height
}));
const labelBounds = estimateEdgeLabelBounds(
edges,
layoutById,
(edge) => erDiagramEdgeToInternalEdge(edge).cardinality ?? null
);
return computeSceneBounds(nodeBounds, labelBounds, CANVAS_PADDING2);
}
function createFitContentBounds2(nodes) {
if (nodes.length === 0) {
return void 0;
}
return {
top: Math.min(...nodes.map((node) => node.y)),
bottom: Math.max(...nodes.map((node) => node.y + node.height))
};
}
function measureNodeHeight2(object) {
if (!object) {
return HEADER_HEIGHT2 + NODE_PADDING2 * 2 + ROW_HEIGHT2;
}
const attributeRows = object.fileType === "er-entity" ? Math.max(getVisibleErColumns(object.columns).length, 1) : Math.max(object.attributes.length, 1);
return HEADER_HEIGHT2 + SECTION_TITLE_HEIGHT2 + attributeRows * ROW_HEIGHT2 + NODE_PADDING2 * 2 + 16;
}
function createSvgSurface2(width, height) {
const svg = activeDocument.createElementNS(SVG_NS3, "svg");
svg.setAttribute("width", String(width));
svg.setAttribute("height", String(height));
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
svg.setAttribute("class", "model-weave-diagram-svg");
return svg;
}
function createMarkerDefinitions2() {
const defs = activeDocument.createElementNS(SVG_NS3, "defs");
defs.appendChild(
createTriangleMarker2("mdspec-er-arrow", DIAGRAM_EDGE2, DIAGRAM_EDGE2)
);
defs.appendChild(
createDiamondMarker2("mdspec-er-diamond-open", "none", DIAGRAM_EDGE2)
);
defs.appendChild(
createDiamondMarker2(
"mdspec-er-diamond-solid",
DIAGRAM_EDGE2,
DIAGRAM_EDGE2
)
);
return defs;
}
function createTriangleMarker2(id, fill, stroke) {
const marker = activeDocument.createElementNS(SVG_NS3, "marker");
marker.setAttribute("id", id);
marker.setAttribute("markerWidth", String(ER_ARROW_MARKER_WIDTH));
marker.setAttribute("markerHeight", String(ER_ARROW_MARKER_HEIGHT));
marker.setAttribute("refX", String(ER_ARROW_TIP_X));
marker.setAttribute("refY", String(ER_ARROW_TIP_Y));
marker.setAttribute("orient", "auto");
marker.setAttribute("markerUnits", "userSpaceOnUse");
const path2 = activeDocument.createElementNS(SVG_NS3, "path");
path2.setAttribute(
"d",
`M 0 0 L ${ER_ARROW_TIP_X} ${ER_ARROW_TIP_Y} L 0 ${ER_ARROW_MARKER_HEIGHT} z`
);
path2.setAttribute("fill", fill);
path2.setAttribute("stroke", stroke);
path2.setAttribute("stroke-width", "1.2");
marker.appendChild(path2);
return marker;
}
function createDiamondMarker2(id, fill, stroke) {
const marker = activeDocument.createElementNS(SVG_NS3, "marker");
marker.setAttribute("id", id);
marker.setAttribute("markerWidth", String(ER_DIAMOND_MARKER_WIDTH));
marker.setAttribute("markerHeight", String(ER_DIAMOND_MARKER_HEIGHT));
marker.setAttribute("refX", String(ER_DIAMOND_TIP_X));
marker.setAttribute("refY", String(ER_DIAMOND_TIP_Y));
marker.setAttribute("orient", "auto");
marker.setAttribute("markerUnits", "strokeWidth");
const path2 = activeDocument.createElementNS(SVG_NS3, "path");
path2.setAttribute(
"d",
`M 0 ${ER_DIAMOND_TIP_Y} L 4 0 L ${ER_DIAMOND_TIP_X} ${ER_DIAMOND_TIP_Y} L 4 ${ER_DIAMOND_MARKER_HEIGHT} z`
);
path2.setAttribute("fill", fill);
path2.setAttribute("stroke", stroke);
path2.setAttribute("stroke-width", "1.2");
marker.appendChild(path2);
return marker;
}
function renderEdge2(edge, layoutById) {
const source = layoutById[edge.source];
const target = layoutById[edge.target];
if (!source || !target) {
return null;
}
const group = activeDocument.createElementNS(SVG_NS3, "g");
const basePoints = getConnectionPoints(source, target);
const markers = getMarkerAttributes2(edge.kind);
const { startX, startY, endX, endY, midX, midY } = insetConnectionPoints(
basePoints,
markers
);
const line = activeDocument.createElementNS(SVG_NS3, "line");
line.setAttribute("x1", String(startX));
line.setAttribute("y1", String(startY));
line.setAttribute("x2", String(endX));
line.setAttribute("y2", String(endY));
line.setAttribute("stroke", DIAGRAM_EDGE2);
line.setAttribute("stroke-width", String(ER_EDGE_STROKE_WIDTH));
line.setAttribute("stroke-dasharray", getDashPattern2(edge.kind));
if (markers.start) {
line.setAttribute("marker-start", markers.start);
}
if (markers.end) {
line.setAttribute("marker-end", markers.end);
}
group.appendChild(line);
const internalEdge = erDiagramEdgeToInternalEdge(edge);
const cardinality = internalEdge.cardinality ?? null;
if (cardinality) {
group.appendChild(createErCardinalityBadge(midX, midY - 8, cardinality));
}
return group;
}
function insetConnectionPoints(points, markers) {
const dx = points.endX - points.startX;
const dy = points.endY - points.startY;
const length = Math.hypot(dx, dy);
if (length <= 1e-3) {
return points;
}
const ux = dx / length;
const uy = dy / length;
const desiredStartInset = getMarkerClearance(markers.start);
const desiredEndInset = getMarkerClearance(markers.end);
const maxInsetPerSide = Math.max(0, (length - ER_MIN_EDGE_VISIBLE_LENGTH) / 2);
const startInset = Math.min(desiredStartInset, maxInsetPerSide);
const endInset = Math.min(desiredEndInset, maxInsetPerSide);
const usableLength = length - startInset - endInset;
if (usableLength <= 8) {
return points;
}
const startX = points.startX + ux * startInset;
const startY = points.startY + uy * startInset;
const endX = points.endX - ux * endInset;
const endY = points.endY - uy * endInset;
return {
startX,
startY,
endX,
endY,
midX: (startX + endX) / 2,
midY: (startY + endY) / 2
};
}
function getMarkerClearance(markerRef) {
if (!markerRef) {
return 0;
}
if (markerRef.includes("mdspec-er-arrow")) {
return ER_ARROW_TIP_X + ER_EDGE_STROKE_WIDTH + ER_NODE_BORDER_WIDTH + ER_ARROW_EXTRA_PADDING;
}
if (markerRef.includes("mdspec-er-diamond-open") || markerRef.includes("mdspec-er-diamond-solid")) {
return ER_DIAMOND_TIP_X + ER_EDGE_STROKE_WIDTH + ER_NODE_BORDER_WIDTH + ER_DIAMOND_EXTRA_PADDING;
}
return ER_EDGE_STROKE_WIDTH + ER_NODE_BORDER_WIDTH + ER_ARROW_EXTRA_PADDING;
}
function getDashPattern2(kind) {
switch (kind) {
case "dependency":
case "implementation":
return "8 6";
default:
return "0";
}
}
function getMarkerAttributes2(kind) {
switch (kind) {
case "composition":
return { start: "url(#mdspec-er-diamond-solid)" };
case "aggregation":
return { start: "url(#mdspec-er-diamond-open)" };
case "association":
case "reference":
case "flow":
case "dependency":
case "implementation":
case "inheritance":
default:
return { end: "url(#mdspec-er-arrow)" };
}
}
function createEntityBox(layout, options) {
const box = activeDocument.createElement("article");
box.addClass("model-weave-node");
box.addClass("model-weave-node-er");
box.setCssProps({
"--mw-node-x": `${layout.x}px`,
"--mw-node-y": `${layout.y}px`,
"--mw-node-width": `${layout.width}px`,
"--mw-node-height": `${layout.height}px`
});
if (!layout.node.object) {
box.appendChild(createFallbackNode2(layout.node.label ?? layout.node.ref ?? layout.node.id));
return box;
}
attachErNodeHoverPreview(box, layout, options);
if (options?.onOpenObject) {
box.addClass("model-weave-node-clickable");
box.setAttribute("role", "button");
box.setAttribute("tabindex", "0");
box.title = `Open ${layout.node.label ?? (layout.node.object.fileType === "er-entity" ? layout.node.object.logicalName : layout.node.object.name)}`;
box.addEventListener("click", (event) => {
if (event.defaultPrevented) {
return;
}
options.onOpenObject?.(layout.node.id, {
openInNewLeaf: event.ctrlKey || event.metaKey
});
});
box.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
options.onOpenObject?.(layout.node.id, { openInNewLeaf: false });
}
});
box.addEventListener("pointerdown", (event) => {
event.stopPropagation();
});
}
const object = layout.node.object;
const header = activeDocument.createElement("header");
header.addClass("model-weave-node-header");
header.addClass("model-weave-node-header-er");
const kind = activeDocument.createElement("div");
kind.addClass("model-weave-node-kind");
kind.textContent = object.fileType === "er-entity" ? "er_entity" : "entity";
const title = activeDocument.createElement("div");
title.addClass("model-weave-node-title");
title.addClass("model-weave-node-er-logical");
title.textContent = layout.node.label ?? (object.fileType === "er-entity" ? object.logicalName : object.name);
header.append(kind, title);
box.appendChild(header);
if (object.fileType === "er-entity") {
const physical = activeDocument.createElement("div");
physical.addClass("model-weave-node-er-physical");
physical.textContent = object.physicalName;
box.appendChild(physical);
box.appendChild(createAttributeSection(getVisibleErColumns(object.columns)));
return box;
}
box.appendChild(
createAttributeSection(
object.attributes.map((attribute) => {
const detail = attribute.type ? `: ${attribute.type}` : "";
return `${attribute.name}${detail}`;
})
)
);
return box;
}
function attachErNodeHoverPreview(box, layout, options) {
if (!options?.app || options.forExport) {
return;
}
const target = createErNodeInteractionTarget(layout, options.interactionSourcePath);
if (!target) {
return;
}
attachGraphElementHoverPreview({
app: options.app,
targetEl: box,
target,
source: "model-weave"
});
}
function createErNodeInteractionTarget(layout, sourcePath) {
const object = layout.node.object;
if (!object?.path) {
return null;
}
const label = object.fileType === "er-entity" ? object.logicalName || object.physicalName || layout.node.label || layout.node.id : object.name || layout.node.label || layout.node.id;
const resolvedSourcePath = sourcePath ?? object.path;
const targetType = object.fileType === "er-entity" ? "er_entity" : "class";
const kind = object.path === resolvedSourcePath ? targetType === "er_entity" ? "er-current" : "class-current" : targetType === "er_entity" ? "er-reference" : "class-reference";
return {
mermaidId: layout.node.id,
linktext: object.path,
sourcePath: resolvedSourcePath,
label,
kind,
targetType,
filePath: object.path,
modelId: layout.node.id,
modelType: object.fileType
};
}
function createAttributeSection(items) {
const section = activeDocument.createElement("section");
section.addClass("model-weave-node-section");
const heading = activeDocument.createElement("div");
heading.addClass("model-weave-node-section-heading");
heading.textContent = "Columns";
section.appendChild(heading);
if (items.length === 0) {
const empty = activeDocument.createElement("div");
empty.addClass("model-weave-node-empty");
empty.textContent = "None";
section.appendChild(empty);
return section;
}
const list = activeDocument.createElement("ul");
list.addClass("model-weave-node-list");
for (const item of items) {
const entry = activeDocument.createElement("li");
entry.textContent = item;
list.appendChild(entry);
}
section.appendChild(list);
return section;
}
function createRelationTable(diagram) {
const section = activeDocument.createElement("details");
section.addClass("model-weave-diagram-details");
section.open = false;
const summary = activeDocument.createElement("summary");
summary.textContent = `Resolved relations (${diagram.edges.length})`;
summary.addClass("model-weave-diagram-details-summary");
section.appendChild(summary);
if (diagram.edges.length === 0) {
const empty = activeDocument.createElement("p");
empty.textContent = modelWeaveText(
"No relations are currently used for display.",
"\u8868\u793A\u5BFE\u8C61\u306E relation \u306F\u3042\u308A\u307E\u305B\u3093\u3002"
);
empty.addClass("model-weave-diagram-details-empty");
section.appendChild(empty);
return section;
}
const list = activeDocument.createElement("ul");
list.addClass("model-weave-diagram-details-list");
const sortedEdges = [...diagram.edges].sort(compareErEdges);
for (const edge of sortedEdges) {
const internalEdge = erDiagramEdgeToInternalEdge(edge);
const columns = internalEdge.mappings.map((mapping) => `${mapping.localColumn} -> ${mapping.targetColumn}`).join(" / ");
const item = activeDocument.createElement("li");
item.addClass("model-weave-diagram-details-item");
item.textContent = `${internalEdge.id || "-"} / ${internalEdge.sourceEntity} -> ${internalEdge.targetEntity} / ${internalEdge.kind || "-"} / ${internalEdge.cardinality || "-"}${internalEdge.notes ? ` / ${internalEdge.notes}` : ""} / ${columns || "-"}`;
list.appendChild(item);
}
section.appendChild(list);
return section;
}
function compareErEdges(left, right) {
const leftEdge = erDiagramEdgeToInternalEdge(left);
const rightEdge = erDiagramEdgeToInternalEdge(right);
const sourceCompare = leftEdge.sourceEntity.localeCompare(rightEdge.sourceEntity);
if (sourceCompare !== 0) {
return sourceCompare;
}
const targetCompare = leftEdge.targetEntity.localeCompare(rightEdge.targetEntity);
if (targetCompare !== 0) {
return targetCompare;
}
return (leftEdge.id || "").localeCompare(rightEdge.id || "");
}
function createFallbackNode2(id) {
const box = activeDocument.createElement("div");
box.addClass("model-weave-node-empty");
box.textContent = `Unresolved entity: ${id}`;
return box;
}
// src/renderers/class-er-mermaid.ts
var CLASS_NODE_CLASS = "mwClass";
var ER_NODE_CLASS = "mwEntity";
var MERMAID_CLASS_ATTRIBUTE_LIMIT = 5;
var MERMAID_CLASS_METHOD_LIMIT = 5;
var ER_MERMAID_READABLE_STYLE_ID = "model-weave-er-mermaid-readable-style";
var ER_DETAIL_INTERACTION_NODE_SELECTOR = "g.node, g.entityBox";
function renderClassMermaidDiagram(diagram, options) {
return renderReducedMermaidDiagram({
className: "mdspec-diagram mdspec-diagram--class",
title: options?.hideTitle ? void 0 : `${diagram.diagram.name} (class / mermaid)`,
renderIdPrefix: "model_weave_class",
source: buildClassOverviewMermaidSource(diagram),
interactionTargets: buildClassOverviewInteractionTargets(
diagram,
options?.interactionSourcePath ?? diagram.diagram.path
),
options,
fallback: () => renderClassDiagram(diagram, options),
fallbackMessage: modelWeaveText(
"Mermaid class overview could not be rendered. Falling back to the custom class renderer.",
"Mermaid \u306E class overview \u3092\u63CF\u753B\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002custom class renderer \u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u3002"
)
});
}
function renderClassMermaidDetailDiagram(diagram, options) {
return renderReducedMermaidDiagram({
className: "mdspec-diagram mdspec-diagram--class",
title: options?.hideTitle ? void 0 : `${diagram.diagram.name} (class / mermaid detail)`,
renderIdPrefix: "model_weave_class_detail",
source: buildClassDetailMermaidSource(diagram),
interactionTargets: buildClassDetailInteractionTargets(
diagram,
options?.interactionSourcePath ?? diagram.diagram.path
),
options,
fallback: () => renderClassDiagram(diagram, options),
fallbackMessage: modelWeaveText(
"Mermaid Detail class overview could not be rendered. Falling back to the custom class renderer.",
"Mermaid Detail \u306E class overview \u3092\u63CF\u753B\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002custom class renderer \u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u3002"
)
});
}
function renderErMermaidDiagram(diagram, options) {
return renderReducedMermaidDiagram({
className: "mdspec-diagram mdspec-diagram--er",
title: options?.hideTitle ? void 0 : `${diagram.diagram.name} (er / mermaid)`,
renderIdPrefix: "model_weave_er",
source: buildErOverviewMermaidSource(diagram),
interactionTargets: buildErOverviewInteractionTargets(
diagram,
options?.interactionSourcePath ?? diagram.diagram.path
),
options,
fallback: () => renderErDiagram(diagram, options),
fallbackMessage: modelWeaveText(
"Mermaid ER overview could not be rendered. Falling back to the custom ER renderer.",
"Mermaid \u306E ER overview \u3092\u63CF\u753B\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002custom ER renderer \u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u3002"
)
});
}
function renderErMermaidDetailDiagram(diagram, options) {
return renderReducedMermaidDiagram({
className: "mdspec-diagram mdspec-diagram--er mdspec-diagram--er-detail",
title: options?.hideTitle ? void 0 : `${diagram.diagram.name} (er / mermaid detail)`,
renderIdPrefix: "model_weave_er_detail",
source: buildErDetailMermaidSource(diagram),
interactionTargets: buildErDetailInteractionTargets(
diagram,
options?.interactionSourcePath ?? diagram.diagram.path
),
interactionNodeSelector: ER_DETAIL_INTERACTION_NODE_SELECTOR,
interactionFindNodeElements: findErDetailMermaidInteractionNodes,
interactionDebugName: "ER detail",
options,
fallback: () => renderErDiagram(diagram, options),
afterRenderSvg: applyErMermaidReadableSvgStyle,
fallbackMessage: modelWeaveText(
"Mermaid Detail ER overview could not be rendered. Falling back to the custom ER renderer.",
"Mermaid Detail \u306E ER overview \u3092\u63CF\u753B\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002custom ER renderer \u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u3002"
)
});
}
function renderReducedMermaidDiagram(config) {
const shell3 = createMermaidShell({
className: config.className,
title: config.title,
forExport: config.options?.forExport,
onExportPng: config.options?.onExportPng,
onExportAndOpenPng: config.options?.onExportAndOpenPng,
exportPngLabel: config.options?.exportPngLabel,
exportPngTitle: config.options?.exportPngTitle,
exportAndOpenPngLabel: config.options?.exportAndOpenPngLabel,
exportAndOpenPngTitle: config.options?.exportAndOpenPngTitle
});
const ready = renderMermaidSourceIntoShell(shell3, {
source: config.source,
renderIdPrefix: config.renderIdPrefix,
nodeSelector: ".node, g.node, foreignObject",
fitVerticalAlign: config.options?.fitVerticalAlign,
viewportState: config.options?.viewportState,
onViewportStateChange: config.options?.onViewportStateChange,
showSourcePanel: !config.options?.forExport,
sourcePanelContainer: config.options?.sourcePanelContainer,
sourcePanelPlacement: config.options?.sourcePanelPlacement,
sourcePanelTitle: config.options?.sourcePanelTitle,
sourcePanelCopyLabel: config.options?.sourcePanelCopyLabel,
showRenderDebug: !config.options?.forExport && config.options?.showMermaidRenderDebug === true
}).then(() => {
const svg = shell3.surface.querySelector("svg");
if (svg?.instanceOf(SVGSVGElement)) {
config.afterRenderSvg?.(svg);
}
const interactionSelector = config.interactionNodeSelector ?? "g.node";
if (!config.options?.forExport && config.options?.showMermaidRenderDebug === true && config.interactionDebugName) {
logMermaidInteractionDebug(
config.interactionDebugName,
svg ?? null,
config.interactionTargets ?? [],
interactionSelector,
config.interactionFindNodeElements,
Boolean(config.options?.app)
);
}
if (!config.options?.forExport && config.options?.app && (config.interactionTargets?.length ?? 0) > 0) {
attachMermaidNodeInteractions({
app: config.options.app,
rootEl: shell3.surface,
targets: config.interactionTargets ?? [],
source: "model-weave",
nodeClassName: "model-weave-mermaid-interactive-node",
nodeSelector: interactionSelector,
findNodeElements: config.interactionFindNodeElements,
dragThreshold: 6,
isDebugEnabled: () => config.options?.showMermaidRenderDebug === true && Boolean(config.interactionDebugName),
debugName: config.interactionDebugName,
formatTitle: (target) => target.label ? `${target.label} (${target.targetType ?? "model"})` : target.linktext
});
}
}).catch(() => {
const fallback = config.fallback();
const notice = createMermaidFallbackNotice(config.fallbackMessage);
shell3.root.replaceChildren(notice, ...Array.from(fallback.childNodes));
});
setMermaidRenderReadyPromise(shell3.root, ready);
return shell3.root;
}
function logMermaidInteractionDebug(debugName, svg, targets, selector, findNodeElements, hasApp) {
const matches = svg ? findNodeElements?.(svg, targets) ?? Array.from(svg.querySelectorAll(selector)).map((element) => ({
element,
target: targets.find((candidate) => element.id === candidate.mermaidId)
})).filter((match) => Boolean(match.target)) : [];
const targetIds = targets.map((target) => target.mermaidId);
const matchedTargetIds = new Set(matches.map((match) => match.target.mermaidId));
const unmatchedTargetIds = targetIds.filter((targetId) => !matchedTargetIds.has(targetId));
console.debug("Model Weave ER detail interaction debug", {
debugName,
hasSvg: Boolean(svg),
hasApp,
attachEligible: Boolean(svg && hasApp && targets.length > 0),
targetCount: targets.length,
targetIds,
matchedCount: matches.length,
selector: findNodeElements ? void 0 : selector,
resolver: findNodeElements ? "er-detail-svg-id" : void 0,
matchedIds: matches.map((match) => ({
targetId: match.target.mermaidId,
elementId: match.element.id,
tagName: match.element.tagName,
className: match.element.getAttribute("class")
})),
unmatchedTargetIds
});
}
function findErDetailMermaidInteractionNodes(svg, targets) {
const entityGroups = getErDetailEntityGroupElements(svg);
const matches = [];
const matchedElements = /* @__PURE__ */ new Set();
for (const target of targets) {
const match = entityGroups.find(
(element) => !matchedElements.has(element) && isErDetailEntityElementForTarget(element, target.mermaidId)
);
if (!match) {
continue;
}
matchedElements.add(match);
matches.push({
element: match,
target
});
}
return matches;
}
function getErDetailEntityGroupElements(svg) {
return Array.from(svg.querySelectorAll(
'g[id^="entity-"], g[id^="er-entity-"]'
));
}
function isErDetailEntityElementForTarget(element, targetId) {
const targetCanonicalId = canonicalizeErDetailEntityId(targetId);
const candidates = getErDetailSvgIdCandidates(element.id);
const matched = candidates.some(
(candidate) => canonicalizeErDetailEntityId(candidate) === targetCanonicalId
);
if (!matched) {
return false;
}
return getErDetailSvgElementExclusionReason(element) === null;
}
function getErDetailSvgElementExclusionReason(element) {
const className = element.getAttribute("class") ?? "";
const identity = `${element.id} ${className}`.toLowerCase();
if (/edge|relationship|cardinality|label|attr|attribute|marker|style/.test(identity)) {
return "edge/relationship/cardinality/label/attribute/marker/style";
}
if (element.id.toLowerCase().includes("-attr-")) {
return "attribute row";
}
if (element.tagName.toLowerCase() !== "g") {
return "not entity group";
}
if (!/entity|er/.test(identity)) {
return "missing entity/er id or class hint";
}
return null;
}
function getErDetailSvgIdCandidates(id) {
const stripped = stripErDetailEntitySvgId(id);
const candidates = /* @__PURE__ */ new Set();
candidates.add(id);
candidates.add(stripped);
candidates.add(canonicalizeErDetailEntityId(stripped));
return Array.from(candidates).filter((candidate) => candidate.length > 0);
}
function stripErDetailEntitySvgId(value) {
return value.replace(/^text-entity-/, "").replace(/^er-entity-/, "").replace(/^entity-/, "").replace(/^er-/, "").replace(
/-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:-.+)?$/i,
""
);
}
function canonicalizeErDetailEntityId(value) {
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
}
function buildClassOverviewInteractionTargets(diagram, sourcePath) {
return buildDiagramNodeInteractionTargets(
diagram,
sourcePath,
(node, usedIds) => ensureUniqueMermaidId(sanitizeMermaidId(node.id), usedIds),
"class"
);
}
function buildClassDetailInteractionTargets(diagram, sourcePath) {
return buildDiagramNodeInteractionTargets(
diagram,
sourcePath,
(node, usedIds) => ensureUniqueMermaidId(sanitizeMermaidId(node.id), usedIds),
"class"
);
}
function buildErOverviewInteractionTargets(diagram, sourcePath) {
return buildDiagramNodeInteractionTargets(
diagram,
sourcePath,
(node, usedIds) => ensureUniqueMermaidId(sanitizeMermaidId(node.id), usedIds),
"er"
);
}
function buildErDetailInteractionTargets(diagram, sourcePath) {
return buildDiagramNodeInteractionTargets(
diagram,
sourcePath,
(node, usedIds) => {
const entity = node.object && node.object.fileType === "er-entity" ? node.object : void 0;
return ensureUniqueMermaidId(
sanitizeMermaidId(entity?.physicalName || entity?.id || node.id),
usedIds
);
},
"er"
);
}
function buildDiagramNodeInteractionTargets(diagram, sourcePath, getMermaidId, mode) {
const usedIds = /* @__PURE__ */ new Set();
return diagram.nodes.map((node) => {
const mermaidId = getMermaidId(node, usedIds);
return mode === "class" ? buildClassNodeInteractionTarget(node, mermaidId, sourcePath) : buildErNodeInteractionTarget(node, mermaidId, sourcePath);
}).filter((target) => Boolean(target));
}
function buildClassNodeInteractionTarget(node, mermaidId, sourcePath) {
const object = node.object && node.object.fileType === "object" ? node.object : void 0;
if (!object?.path) {
return null;
}
const label = node.label ?? object.name ?? node.id;
return {
mermaidId,
linktext: object.path,
sourcePath,
label,
kind: object.path === sourcePath ? "class-current" : "class-node",
targetType: "class",
filePath: object.path,
modelId: getClassObjectId2(object) ?? node.id,
modelType: object.fileType
};
}
function buildErNodeInteractionTarget(node, mermaidId, sourcePath) {
const entity = node.object && node.object.fileType === "er-entity" ? node.object : void 0;
if (!entity?.path) {
return null;
}
const label = node.label ?? entity.logicalName ?? entity.physicalName ?? node.id;
return {
mermaidId,
linktext: entity.path,
sourcePath,
label,
kind: entity.path === sourcePath ? "er-current" : "er-entity-node",
targetType: "er_entity",
filePath: entity.path,
modelId: entity.id || node.id,
modelType: entity.fileType
};
}
function buildClassOverviewMermaidSource(diagram) {
const palette = getModelWeaveMermaidPalette();
const lines = [
"flowchart LR",
` ${buildModelWeaveMermaidClassDef(CLASS_NODE_CLASS, palette.classFill, palette.classBorder)}`
];
const nodeIds = /* @__PURE__ */ new Map();
const usedNodeIds = /* @__PURE__ */ new Set();
for (const node of diagram.nodes) {
const object = node.object && node.object.fileType === "object" ? node.object : void 0;
const mermaidId = ensureUniqueMermaidId(sanitizeMermaidId(node.id), usedNodeIds);
nodeIds.set(node.id, mermaidId);
lines.push(` ${mermaidId}["${buildClassOverviewNodeLabel(node.label, object, node.id)}"]:::${CLASS_NODE_CLASS}`);
}
for (const edge of diagram.edges) {
const from = nodeIds.get(edge.source);
const to = nodeIds.get(edge.target);
if (!from || !to) {
continue;
}
const label = sanitizeEdgeLabel2(buildClassEdgeLabel(edge));
lines.push(label ? ` ${from} -->|${label}| ${to}` : ` ${from} --> ${to}`);
}
return lines.join("\n");
}
function buildClassDetailMermaidSource(diagram) {
const lines = ["classDiagram"];
const nodeIds = /* @__PURE__ */ new Map();
const usedNodeIds = /* @__PURE__ */ new Set();
for (const node of diagram.nodes) {
const object = node.object && node.object.fileType === "object" ? node.object : void 0;
const mermaidId = ensureUniqueMermaidId(sanitizeMermaidId(node.id), usedNodeIds);
nodeIds.set(node.id, mermaidId);
lines.push(...buildClassDetailDeclaration(mermaidId, node.label, object, node.id));
}
for (const edge of diagram.edges) {
const from = nodeIds.get(edge.source);
const to = nodeIds.get(edge.target);
if (!from || !to) {
continue;
}
lines.push(buildClassDetailRelation(edge, from, to));
}
return lines.join("\n");
}
function buildErOverviewMermaidSource(diagram) {
const palette = getModelWeaveMermaidPalette();
const lines = [
"flowchart LR",
` ${buildModelWeaveMermaidClassDef(ER_NODE_CLASS, palette.erFill, palette.erBorder)}`
];
const nodeIds = /* @__PURE__ */ new Map();
const usedNodeIds = /* @__PURE__ */ new Set();
for (const node of diagram.nodes) {
const entity = node.object && node.object.fileType === "er-entity" ? node.object : void 0;
const mermaidId = ensureUniqueMermaidId(sanitizeMermaidId(node.id), usedNodeIds);
nodeIds.set(node.id, mermaidId);
lines.push(` ${mermaidId}["${buildErNodeLabel(node.label, entity, node.id)}"]:::${ER_NODE_CLASS}`);
}
for (const edge of diagram.edges) {
const from = nodeIds.get(edge.source);
const to = nodeIds.get(edge.target);
if (!from || !to) {
continue;
}
const label = sanitizeEdgeLabel2(buildErEdgeLabel(edge));
lines.push(label ? ` ${from} -->|${label}| ${to}` : ` ${from} --> ${to}`);
}
return lines.join("\n");
}
function buildErDetailMermaidSource(diagram) {
const lines = ["erDiagram"];
const nodeIds = /* @__PURE__ */ new Map();
const usedNodeIds = /* @__PURE__ */ new Set();
for (const node of diagram.nodes) {
const entity = node.object && node.object.fileType === "er-entity" ? node.object : void 0;
const mermaidId = ensureUniqueMermaidId(
sanitizeMermaidId(entity?.physicalName || entity?.id || node.id),
usedNodeIds
);
nodeIds.set(node.id, mermaidId);
lines.push(...buildErDetailDeclaration(mermaidId, entity));
}
for (const edge of diagram.edges) {
const from = nodeIds.get(edge.source);
const to = nodeIds.get(edge.target);
if (!from || !to) {
continue;
}
lines.push(buildErDetailRelation(edge, from, to));
}
return lines.join("\n");
}
function applyErMermaidReadableSvgStyle(svg) {
if (svg.querySelector(`#${ER_MERMAID_READABLE_STYLE_ID}`)) {
return;
}
const style = svg.ownerDocument.createElementNS(
"http://www.w3.org/2000/svg",
"style"
);
style.setAttribute("id", ER_MERMAID_READABLE_STYLE_ID);
style.textContent = buildErMermaidReadableSvgStyle();
svg.prepend(style);
}
function buildErMermaidReadableSvgStyle() {
return [
".er.entityBox,.entityBox,.er.attributeBoxOdd,.attributeBoxOdd,.er.attributeBoxEven,.attributeBoxEven{fill:#f8fafc!important;stroke:#64748b!important;}",
".er.relationshipLabelBox,.relationshipLabelBox{fill:#f8fafc!important;stroke:#cbd5e1!important;}",
".er.entityLabel,.entityLabel,.er.attribute-type,.attribute-type,.er.attribute-name,.attribute-name,.er.attribute-key,.attribute-key,.er.relationshipLabel,.relationshipLabel{fill:#111827!important;color:#111827!important;}",
"text,tspan{fill:#111827!important;color:#111827!important;}"
].join("\n");
}
function buildClassOverviewNodeLabel(explicitLabel, object, fallbackId) {
return escapeMermaidLabel(explicitLabel?.trim() || object?.name || fallbackId);
}
function buildErNodeLabel(explicitLabel, entity, fallbackId) {
if (!entity) {
return escapeMermaidLabel(explicitLabel?.trim() || fallbackId);
}
const lines = [entity.logicalName || explicitLabel?.trim() || fallbackId];
if (entity.physicalName) {
lines.push(entity.physicalName);
}
return escapeMermaidLabel(lines.join("\n"));
}
function buildClassEdgeLabel(edge) {
const internal = classDiagramEdgeToInternalEdge(edge);
const base = internal.label?.trim() || internal.kind || null;
const multiplicity = internal.fromMultiplicity || internal.toMultiplicity ? `${internal.fromMultiplicity ?? "-"}\u2192${internal.toMultiplicity ?? "-"}` : null;
if (base && multiplicity) {
return `${base} (${multiplicity})`;
}
return base ?? multiplicity;
}
function buildErEdgeLabel(edge) {
const internal = erDiagramEdgeToInternalEdge(edge);
return internal.cardinality?.trim() || internal.label?.trim() || internal.id?.trim() || internal.kind?.trim() || null;
}
function sanitizeEdgeLabel2(value) {
if (!value) {
return null;
}
return escapeMermaidEdgeLabel(value);
}
function buildErDetailDeclaration(mermaidId, entity) {
const columns = entity ? buildErColumnLines(entity) : [];
if (columns.length === 0) {
return [` ${mermaidId} {`, " }"];
}
return [
` ${mermaidId} {`,
...columns.map((column) => ` ${column}`),
" }"
];
}
function buildErColumnLines(entity) {
const fkColumns = getErForeignKeyColumns(entity);
const columns = entity.columns.slice(0, MERMAID_CLASS_ATTRIBUTE_LIMIT).map((column) => formatErColumn(column, fkColumns)).filter((column) => Boolean(column));
if (entity.columns.length > MERMAID_CLASS_ATTRIBUTE_LIMIT) {
columns.push("string more_columns");
}
return columns;
}
function formatErColumn(column, fkColumns) {
const name = formatErIdentifierToken(column.physicalName || column.logicalName);
if (!name) {
return null;
}
const type = formatErTypeToken(column.dataType || "string");
const keys = [
column.pk ? "PK" : null,
isErForeignKeyColumn(column, fkColumns) ? "FK" : null
].filter((key) => Boolean(key));
return `${type} ${name}${keys.length > 0 ? ` ${keys.join(",")}` : ""}`;
}
function getErForeignKeyColumns(entity) {
const columns = /* @__PURE__ */ new Set();
for (const relation of entity.outboundRelations) {
for (const mapping of relation.mappings) {
if (mapping.localColumn.trim()) {
columns.add(mapping.localColumn.trim());
}
}
}
return columns;
}
function isErForeignKeyColumn(column, fkColumns) {
return fkColumns.has(column.physicalName) || fkColumns.has(column.logicalName);
}
function buildErDetailRelation(edge, from, to) {
const internal = erDiagramEdgeToInternalEdge(edge);
const markers = getErRelationshipMarkers(internal);
const label = sanitizeEdgeLabel2(
internal.label?.trim() || internal.id?.trim() || internal.kind?.trim() || null
) ?? "relates";
return ` ${from} ${markers.left}--${markers.right} ${to} : ${label}`;
}
function getErRelationshipMarkers(edge) {
const cardinality = edge.cardinality?.trim();
if (cardinality) {
const normalized = cardinality.toLowerCase().replace(/\s+/g, "");
switch (normalized) {
case "many-to-one":
case "manytoone":
case "n-1":
case "*-1":
return { left: "}o", right: "||" };
case "one-to-many":
case "onetomany":
case "1-n":
case "1-*":
return { left: "||", right: "o{" };
case "one-to-one":
case "onetoone":
case "1-1":
return { left: "||", right: "||" };
default:
break;
}
const split = normalized.match(/^(.+?)(?:-|:|to)(.+)$/);
if (split) {
return {
left: getLeftErCardinalityMarker(split[1]),
right: getRightErCardinalityMarker(split[2])
};
}
}
return { left: "||", right: "o{" };
}
function getLeftErCardinalityMarker(value) {
switch (normalizeErCardinalityPart(value)) {
case "zero-or-one":
return "o|";
case "one-or-more":
return "}|";
case "many":
case "zero-or-more":
return "}o";
case "one":
default:
return "||";
}
}
function getRightErCardinalityMarker(value) {
switch (normalizeErCardinalityPart(value)) {
case "zero-or-one":
return "|o";
case "one-or-more":
return "|{";
case "many":
case "zero-or-more":
return "o{";
case "one":
default:
return "||";
}
}
function normalizeErCardinalityPart(value) {
const normalized = value?.trim().toLowerCase() ?? "";
switch (normalized) {
case "1":
case "one":
case "single":
return "one";
case "0..1":
case "0..one":
case "zero-or-one":
case "optional":
return "zero-or-one";
case "1..*":
case "1..n":
case "one-or-more":
return "one-or-more";
case "*":
case "n":
case "many":
return "many";
case "0..*":
case "0..n":
case "zero-or-more":
default:
return normalized.includes("many") || normalized === "*" || normalized === "n" ? "many" : normalized.includes("0") && (normalized.includes("*") || normalized.includes("n")) ? "zero-or-more" : normalized.includes("1") ? "one" : "many";
}
}
function formatErIdentifierToken(value) {
return sanitizeMermaidId(formatMermaidMember(value));
}
function formatErTypeToken(value) {
const formatted = formatMermaidMember(value) || "string";
const token = formatted.replace(/[^A-Za-z0-9_]/g, "_").replace(/^_+|_+$/g, "");
if (!token) {
return "string";
}
return /^[A-Za-z]/.test(token) ? token : `T_${token}`;
}
function buildClassDetailDeclaration(mermaidId, explicitLabel, object, fallbackId) {
const displayName = explicitLabel?.trim() || object?.name || fallbackId;
const label = escapeMermaidClassText(displayName);
const members = object ? buildClassDetailMemberLines(object, displayName) : [];
if (members.length === 0) {
return [` class ${mermaidId}["${label}"]`];
}
return [
` class ${mermaidId}["${label}"] {`,
...members.map((member) => ` ${member}`),
" }"
];
}
function buildClassDetailMemberLines(object, displayName) {
const lines = [];
const objectId = getClassObjectId2(object);
if (objectId && objectId !== displayName) {
lines.push(`id: ${formatMermaidMember(objectId)}`);
}
lines.push(...buildClassMemberLines(object));
return lines;
}
function buildClassDetailRelation(edge, from, to) {
const internal = classDiagramEdgeToInternalEdge(edge);
const arrow = getClassDiagramArrow(internal.kind);
const fromMultiplicity = formatClassMultiplicity(internal.fromMultiplicity);
const toMultiplicity = formatClassMultiplicity(internal.toMultiplicity);
const label = sanitizeEdgeLabel2(buildClassEdgeLabel(edge));
const multiplicities = fromMultiplicity || toMultiplicity ? ` "${fromMultiplicity ?? ""}" ${arrow} "${toMultiplicity ?? ""}" ` : ` ${arrow} `;
return label ? ` ${from}${multiplicities}${to} : ${label}` : ` ${from}${multiplicities}${to}`;
}
function getClassDiagramArrow(kind) {
switch (kind) {
case "inheritance":
return "--|>";
case "implementation":
return "..|>";
case "dependency":
return "..>";
case "composition":
return "*--";
case "aggregation":
return "o--";
case "association":
case "reference":
case "flow":
default:
return "-->";
}
}
function formatClassMultiplicity(value) {
if (!value?.trim()) {
return null;
}
return escapeMermaidClassText(value);
}
function getClassObjectId2(object) {
const rawId = object.frontmatter.id;
if (typeof rawId !== "string") {
return null;
}
const id = rawId.trim();
return id || null;
}
function buildClassMemberLines(object) {
const lines = object.attributes.slice(0, MERMAID_CLASS_ATTRIBUTE_LIMIT).map(formatClassAttribute).filter((line) => Boolean(line));
if (object.attributes.length > MERMAID_CLASS_ATTRIBUTE_LIMIT) {
lines.push("...");
}
lines.push(
...object.methods.slice(0, MERMAID_CLASS_METHOD_LIMIT).map(formatClassMethod).filter((line) => Boolean(line))
);
if (object.methods.length > MERMAID_CLASS_METHOD_LIMIT) {
lines.push("...");
}
return lines;
}
function formatClassAttribute(attribute) {
const name = formatMermaidMember(attribute.name);
if (!name) {
return null;
}
const visibility = formatVisibility(attribute.visibility);
const type = attribute.type ? `: ${formatMermaidMember(attribute.type)}` : "";
const required = attribute.required === true ? " required" : "";
const multiplicity = attribute.multiplicity ? ` ${formatMermaidMember(attribute.multiplicity)}` : "";
return `${visibility}${name}${type}${multiplicity}${required}`;
}
function formatClassMethod(method) {
const name = formatMermaidMember(method.name);
if (!name) {
return null;
}
const visibility = formatVisibility(method.visibility);
const parameters = method.parameters.map((parameter) => {
const parameterName = formatMermaidMember(parameter.name);
if (!parameterName) {
return null;
}
const type = parameter.type ? `: ${formatMermaidMember(parameter.type)}` : "";
return `${parameterName}${type}`;
}).filter((parameter) => Boolean(parameter)).join(", ");
const returnType = method.returnType ? `: ${formatMermaidMember(method.returnType)}` : "";
return `${visibility}${name}(${parameters})${returnType}`;
}
function formatVisibility(visibility) {
switch (visibility) {
case "public":
return "+ ";
case "protected":
return "# ";
case "private":
return "- ";
case "package":
return "~ ";
default:
return "";
}
}
function escapeMermaidClassText(value) {
return escapeMermaidLabel(value).replace(/
/g, " ");
}
// src/renderers/component-renderer.ts
function renderComponentDiagram(diagram) {
const root = activeDocument.createElement("section");
root.className = "mdspec-diagram mdspec-diagram--component";
const title = activeDocument.createElement("h2");
title.textContent = `${diagram.diagram.name} (component)`;
root.appendChild(title);
const grid = activeDocument.createElement("div");
grid.className = "mdspec-component-grid";
for (const node of diagram.nodes) {
const box = activeDocument.createElement("article");
box.className = "mdspec-component";
const heading = activeDocument.createElement("h3");
heading.textContent = getNodeLabel(node);
box.appendChild(heading);
const description = activeDocument.createElement("p");
description.textContent = getNodeDescription(node);
box.appendChild(description);
grid.appendChild(box);
}
if (grid.childElementCount === 0) {
const empty = activeDocument.createElement("p");
empty.textContent = "No components resolved.";
root.appendChild(empty);
} else {
root.appendChild(grid);
}
return root;
}
function getNodeLabel(node) {
if (!node.object) {
return node.ref ?? node.id;
}
return node.object.fileType === "er-entity" ? node.object.logicalName : node.object.name;
}
function getNodeDescription(node) {
if (!node.object) {
return "No component description available.";
}
if (node.object.fileType === "er-entity") {
return node.object.physicalName;
}
if (node.object.fileType === "dfd-object") {
return node.object.kind;
}
return node.object.description ?? "No component description available.";
}
// src/renderers/dfd-mermaid.ts
function renderDfdMermaidDiagram(diagram, options) {
const shell3 = createMermaidShell({
className: isFlowDiagramModel(diagram.diagram) ? "mdspec-diagram mdspec-diagram--flow-diagram" : "mdspec-diagram mdspec-diagram--dfd",
title: options?.hideTitle ? void 0 : isFlowDiagramModel(diagram.diagram) ? `${diagram.diagram.name} (flow diagram)` : `${diagram.diagram.name} (dfd)`,
forExport: options?.forExport,
onExportPng: options?.onExportPng,
onExportAndOpenPng: options?.onExportAndOpenPng,
exportPngLabel: options?.exportPngLabel,
exportPngTitle: options?.exportPngTitle,
exportAndOpenPngLabel: options?.exportAndOpenPngLabel,
exportAndOpenPngTitle: options?.exportAndOpenPngTitle
});
const renderedDiagram = getFlowDiagramRenderedDiagram(diagram, options?.flowDiagramViewMode);
if (!options?.hideDetails) {
const domainDetails = createDomainPlacementDetails(renderedDiagram, options?.dfdDetailLabels);
if (domainDetails) {
shell3.root.appendChild(domainDetails);
}
shell3.root.appendChild(createObjectDetails(renderedDiagram, options?.dfdDetailLabels));
shell3.root.appendChild(createFlowDetails(renderedDiagram.edges, options?.dfdDetailLabels));
}
const sourcePath = options?.interactionSourcePath ?? diagram.diagram.path;
const flowHoverMetadata = isFlowDiagramModel(renderedDiagram.diagram) ? buildFlowDiagramHoverMetadata(renderedDiagram, sourcePath) : null;
const interactionTargets = flowHoverMetadata?.objects ?? buildDfdMermaidInteractionTargets(
renderedDiagram,
sourcePath
);
const ready = renderMermaidSourceIntoShell(shell3, {
source: buildDfdMermaidSource(renderedDiagram, options?.colorScheme),
renderIdPrefix: "model_weave_dfd",
fitVerticalAlign: options?.fitVerticalAlign,
viewportState: options?.viewportState,
onViewportStateChange: options?.onViewportStateChange,
showSourcePanel: !options?.forExport,
sourcePanelContainer: options?.sourcePanelContainer,
sourcePanelPlacement: options?.sourcePanelPlacement,
sourcePanelTitle: options?.sourcePanelTitle,
sourcePanelCopyLabel: options?.sourcePanelCopyLabel,
showRenderDebug: !options?.forExport && options?.showMermaidRenderDebug === true
}).then(() => {
if (!options?.forExport && options?.app && flowHoverMetadata) {
attachFlowDiagramFlowHoverPreviews(
shell3.surface,
flowHoverMetadata.flows,
options.app,
options?.showMermaidRenderDebug === true
);
}
if (!options?.forExport && options?.app && interactionTargets.length > 0) {
attachMermaidNodeInteractions({
app: options.app,
rootEl: shell3.surface,
targets: interactionTargets,
source: "model-weave",
nodeClassName: "model-weave-mermaid-interactive-node",
dragThreshold: 6,
isDebugEnabled: () => options?.showMermaidRenderDebug === true,
debugName: isFlowDiagramModel(renderedDiagram.diagram) ? "Flow Diagram Mermaid" : "DFD Mermaid",
formatTitle: (target) => target.label ? `${target.label} (${target.targetType ?? "model"})` : target.linktext,
openLinkText: isFlowDiagramModel(diagram.diagram) ? (target, event) => {
const linktext = getFlowDiagramOpenLinkText(target);
if (!linktext) {
return;
}
return options.app?.workspace.openLinkText(
linktext,
target.sourcePath,
event.ctrlKey || event.metaKey
);
} : void 0
});
}
}).catch(() => {
shell3.root.replaceChildren(
createMermaidFallbackNotice(
modelWeaveText(
"DFD Mermaid rendering failed. Check diagnostics and Mermaid compatibility for this diagram.",
"DFD Mermaid \u306E\u63CF\u753B\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002Diagnostics \u3068 Mermaid \u4E92\u63DB\u6027\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
)
)
);
});
setMermaidRenderReadyPromise(shell3.root, ready);
return shell3.root;
}
function buildFlowDiagramHoverMetadata(diagram, sourcePath) {
if (!isFlowDiagramModel(diagram.diagram)) {
return { objects: [], flows: [] };
}
const objects = diagram.nodes.map((node) => {
const rows = buildFlowDiagramObjectHoverRows(node);
const hoverTitle = "Flow Object";
const target = {
mermaidId: toMermaidNodeId(node.id),
linktext: getFlowDiagramFallbackLinktext(sourcePath),
sourcePath,
label: node.label ?? node.id,
kind: "flow-diagram-object",
targetType: "flow_diagram_object",
filePath: getStringMetadata(node.metadata, "refModelPath"),
modelId: node.id,
modelType: "flow-diagram",
nodeId: node.id,
hoverTitle,
hoverRows: rows,
hoverText: formatHoverText(hoverTitle, rows),
previewLinktext: getResolvedPreviewLinktext(node.ref, getStringMetadata(node.metadata, "refModelPath")),
nativeTooltip: formatFlowDiagramObjectTooltip(node)
};
return target;
});
const flows = diagram.edges.map((edge, index) => {
const data = getStringMetadata(edge.metadata, "dataRaw");
const rows = buildFlowDiagramFlowHoverRows(edge, data);
const hoverTitle = "Flow";
return {
mermaidId: toFlowDiagramFlowMermaidId(edge, index),
linktext: getFlowDiagramFallbackLinktext(sourcePath),
sourcePath,
edgeId: edge.id,
source: edge.source,
target: edge.target,
label: edge.label,
flowKind: getStringMetadata(edge.metadata, "flowKind"),
trigger: getStringMetadata(edge.metadata, "trigger"),
data,
condition: getStringMetadata(edge.metadata, "condition"),
kind: "flow-diagram-flow",
targetType: "flow_diagram_flow",
modelId: edge.id,
modelType: "flow-diagram",
filePath: getStringMetadata(edge.metadata, "dataModelPath"),
hoverTitle,
hoverRows: rows,
hoverText: formatHoverText(hoverTitle, rows),
previewLinktext: getResolvedPreviewLinktext(data, getStringMetadata(edge.metadata, "dataModelPath")),
nativeTooltip: formatFlowDiagramFlowTooltip(edge, data)
};
});
return { objects, flows };
}
function formatFlowDiagramObjectTooltip(node) {
const lines = [
`Flow Object: ${node.id}`,
node.label,
`kind: ${typeof node.kind === "string" ? node.kind : "-"}`,
`domain: ${getStringMetadata(node.metadata, "domain") ?? "-"}`,
`ref: ${node.ref?.trim() || "-"}`
];
const notes = formatDiagramEdgeNotes(node.metadata?.notes);
if (notes) {
lines.push(notes);
}
return lines.filter((line) => Boolean(line && line.trim())).join("\n");
}
function formatFlowDiagramFlowTooltip(edge, data) {
const lines = [
`Flow: ${edge.id ?? "-"}`,
`${edge.source} -> ${edge.target}`,
`kind: ${getStringMetadata(edge.metadata, "flowKind") ?? "-"}`,
`trigger: ${getStringMetadata(edge.metadata, "trigger") ?? "-"}`,
`data: ${data?.trim() || "-"}`,
`condition: ${getStringMetadata(edge.metadata, "condition") ?? "-"}`
];
const notes = formatDiagramEdgeNotes(edge.metadata?.notes);
if (notes) {
lines.push(notes);
}
return lines.join("\n");
}
function buildFlowDiagramObjectHoverRows(node) {
return [
{ label: "id", value: node.id },
{ label: "label", value: node.label },
{ label: "kind", value: typeof node.kind === "string" ? node.kind : void 0 },
{ label: "domain", value: getStringMetadata(node.metadata, "domain") },
{ label: "ref", value: node.ref },
{ label: "notes", value: formatDiagramEdgeNotes(node.metadata?.notes) }
];
}
function buildFlowDiagramFlowHoverRows(edge, data) {
return [
{ label: "id", value: edge.id },
{ label: "from", value: edge.source },
{ label: "to", value: edge.target },
{ label: "kind", value: getStringMetadata(edge.metadata, "flowKind") },
{ label: "trigger", value: getStringMetadata(edge.metadata, "trigger") },
{ label: "data", value: data },
{ label: "condition", value: getStringMetadata(edge.metadata, "condition") },
{ label: "notes", value: formatDiagramEdgeNotes(edge.metadata?.notes) }
];
}
function formatHoverText(title, rows) {
return [
title,
...rows.map((row) => `${row.label}: ${formatHoverValue(row.value)}`)
].join("\n");
}
function formatHoverValue(value) {
const trimmed = value?.trim();
return trimmed || "-";
}
function getStringMetadata(metadata, key) {
const value = metadata?.[key];
return typeof value === "string" ? value : void 0;
}
function toFlowDiagramFlowMermaidId(edge, index) {
const id = edge.id?.trim() || `${edge.source}_${edge.target}_${index + 1}`;
return `FLOW_${index + 1}_${toMermaidNodeId(id)}`;
}
function getResolvedPreviewLinktext(rawReference, resolvedPath) {
if (!resolvedPath) {
return void 0;
}
const trimmed = rawReference?.trim();
if (!trimmed) {
return void 0;
}
const parsed = parseReferenceValue(trimmed);
return parsed?.kind === "wikilink" || parsed?.kind === "markdown_link" ? resolvedPath : void 0;
}
function getFlowDiagramFallbackLinktext(sourcePath) {
return sourcePath;
}
function getFlowDiagramOpenLinkText(target) {
const linktext = target.previewLinktext?.trim() || target.filePath?.trim();
return linktext ? linktext : null;
}
function attachFlowDiagramFlowHoverPreviews(rootEl, flowTargets, app, showMermaidRenderDebug) {
if (flowTargets.length === 0) {
return;
}
const svg = rootEl.querySelector("svg");
if (!svg) {
return;
}
const edgeLabels = Array.from(svg.querySelectorAll("g.edgeLabel"));
flowTargets.forEach((target, index) => {
const labelEl = edgeLabels[index];
if (!labelEl) {
return;
}
setSvgNativeTooltip(labelEl, target.nativeTooltip);
labelEl.addClass("model-weave-mermaid-interactive-flow");
labelEl.setAttribute("data-model-weave-flow-id", target.edgeId ?? target.mermaidId);
attachGraphElementHoverPreview({
app,
targetEl: labelEl,
target,
rootEl,
source: "model-weave",
isDebugEnabled: () => showMermaidRenderDebug,
debugName: "Flow Diagram Mermaid Flow"
});
});
}
function setSvgNativeTooltip(element, text) {
const existingTitle = element.querySelector("title");
const trimmed = text?.trim();
if (!trimmed) {
existingTitle?.remove();
element.removeAttribute("title");
return;
}
const title = existingTitle ?? element.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "title");
title.textContent = trimmed;
if (!title.parentElement) {
element.prepend(title);
}
}
function buildDfdMermaidInteractionTargets(diagram, sourcePath) {
return diagram.nodes.map((node) => {
const object = getDfdObject(node);
if (!object?.path) {
return null;
}
const target = {
mermaidId: toMermaidNodeId(node.id),
linktext: object.path,
sourcePath,
label: node.label ?? object.name ?? node.id,
kind: "dfd-object",
targetType: object.fileType,
filePath: object.path,
modelId: object.id,
modelType: object.fileType
};
return target;
}).filter((target) => Boolean(target));
}
function buildDfdMermaidSource(diagram, colorScheme, flowDiagramViewMode) {
if (isFlowDiagramModel(diagram.diagram)) {
return buildFlowDiagramMermaidSource(getFlowDiagramRenderedDiagram(diagram, flowDiagramViewMode), colorScheme);
}
const palette = getModelWeaveMermaidPalette();
const lines = ["flowchart LR"];
const colorClasses = /* @__PURE__ */ new Map();
const domainStyles = [];
if (!colorScheme) {
lines.push(
` ${buildModelWeaveMermaidClassDef("dfdExternal", palette.dfdExternalFill, palette.dfdExternalBorder, { strokeWidth: 1.5 })}`,
` ${buildModelWeaveMermaidClassDef("dfdProcess", palette.dfdProcessFill, palette.dfdProcessBorder, { strokeWidth: 1.5 })}`,
` ${buildModelWeaveMermaidClassDef("dfdDatastore", palette.dfdDatastoreFill, palette.dfdDatastoreBorder, { strokeWidth: 1.5 })}`,
` ${buildModelWeaveMermaidClassDef("dfdOther", palette.dfdOtherFill, palette.dfdOtherBorder, { strokeWidth: 1.5 })}`
);
}
const nodeIds = /* @__PURE__ */ new Map();
const localDomains = getDfdLocalDomains(diagram);
const localDomainsById = new Map(localDomains.map((domain) => [domain.id, domain]));
const groupedNodes = /* @__PURE__ */ new Map();
const ungroupedNodes = [];
for (const node of diagram.nodes) {
const domainId = getNodeDomainId(node);
if (domainId && localDomainsById.has(domainId)) {
if (!groupedNodes.has(domainId)) {
groupedNodes.set(domainId, []);
}
groupedNodes.get(domainId).push(node);
} else {
ungroupedNodes.push(node);
}
}
for (const root of buildDomainTree(localDomains)) {
appendDfdDomainSubgraph(
lines,
root,
groupedNodes,
nodeIds,
1,
colorScheme,
colorClasses,
domainStyles
);
}
for (const node of ungroupedNodes) {
const mermaidId = toMermaidNodeId(node.id);
nodeIds.set(node.id, mermaidId);
lines.push(` ${mermaidId}${toMermaidNodeDeclaration(
node,
getDfdObject(node),
colorScheme,
colorClasses
)}`);
}
for (const edge of diagram.edges) {
const from = nodeIds.get(edge.source);
const to = nodeIds.get(edge.target);
if (!from || !to) {
continue;
}
const label = sanitizeMermaidEdgeLabel(edge.label);
if (label) {
lines.push(` ${from} -->|${label}| ${to}`);
} else {
lines.push(` ${from} --> ${to}`);
}
}
if (colorClasses.size > 0) {
lines.push("");
for (const [className, style] of colorClasses) {
lines.push(` classDef ${className} ${formatMermaidClassDefStyle(style)}`);
}
}
if (colorClasses.size > 0) {
lines.push("");
for (const [className, style] of colorClasses) {
lines.push(` classDef ${className} ${formatMermaidClassDefStyle(style)}`);
}
}
if (domainStyles.length > 0) {
lines.push("", ...domainStyles);
}
return lines.join("\n");
}
function getFlowDiagramRenderedDiagram(diagram, effectiveViewMode) {
if (!isFlowDiagramModel(diagram.diagram) || effectiveViewMode !== "screen") {
return diagram;
}
return buildFlowDiagramScreenFlowProjection(diagram);
}
function buildFlowDiagramScreenFlowProjection(diagram) {
if (!isFlowDiagramModel(diagram.diagram)) {
return diagram;
}
const visibleNodes = diagram.nodes.filter(isScreenFlowVisibleNode);
if (visibleNodes.length === 0) {
return diagram;
}
const visibleIds = new Set(visibleNodes.map((node) => node.id));
const outgoing = /* @__PURE__ */ new Map();
for (const edge of diagram.edges) {
const edges = outgoing.get(edge.source) ?? [];
edges.push(edge);
outgoing.set(edge.source, edges);
}
const projectedByPair = /* @__PURE__ */ new Map();
for (const sourceNode of visibleNodes) {
const queue = (outgoing.get(sourceNode.id) ?? []).map((edge) => ({
edge,
visitedEdges: /* @__PURE__ */ new Set([getProjectionEdgeVisitKey(edge)])
}));
while (queue.length > 0) {
const current = queue.shift();
if (!current) {
continue;
}
const targetId = current.edge.target;
if (visibleIds.has(targetId)) {
if (targetId !== sourceNode.id) {
addProjectedScreenFlowEdge(projectedByPair, sourceNode.id, targetId, current.edge);
}
continue;
}
for (const nextEdge of outgoing.get(targetId) ?? []) {
const visitKey = getProjectionEdgeVisitKey(nextEdge);
if (current.visitedEdges.has(visitKey)) {
continue;
}
const nextVisitedEdges = new Set(current.visitedEdges);
nextVisitedEdges.add(visitKey);
queue.push({ edge: nextEdge, visitedEdges: nextVisitedEdges });
}
}
}
return {
...diagram,
diagram: {
...diagram.diagram,
flowView: "detail"
},
nodes: visibleNodes,
edges: [...projectedByPair.values()]
};
}
function isScreenFlowVisibleNode(node) {
switch (node.kind) {
case "screen":
case "external":
case "actor":
case "user":
case "context":
case "message":
return true;
default:
return false;
}
}
function addProjectedScreenFlowEdge(projectedByPair, source, target, incomingEdge) {
const key = `${source}->${target}`;
const label = buildScreenFlowProjectionEdgeLabel(incomingEdge);
const existing = projectedByPair.get(key);
if (!existing) {
projectedByPair.set(key, {
id: `screen_flow:${source}->${target}`,
source,
target,
kind: "flow",
label,
metadata: {
...incomingEdge.metadata,
projected: true,
sourceFlowId: incomingEdge.id,
dataRaw: void 0
}
});
return;
}
existing.label = mergeScreenFlowLabels(existing.label, label);
}
function buildScreenFlowProjectionEdgeLabel(edge) {
return getStringMetadata(edge.metadata, "condition")?.trim() || getStringMetadata(edge.metadata, "trigger")?.trim() || getStringMetadata(edge.metadata, "flowKind")?.trim() || void 0;
}
function mergeScreenFlowLabels(left, right) {
const values = [
...(left ?? "").split(" / "),
...(right ?? "").split(" / ")
].map((value) => value.trim()).filter(Boolean);
const unique = [...new Set(values)];
if (unique.length === 0) {
return void 0;
}
const merged = unique.join(" / ");
return merged.length > 80 ? `${merged.slice(0, 77)}...` : merged;
}
function getProjectionEdgeVisitKey(edge) {
return `${edge.id ?? ""}:${edge.source}->${edge.target}`;
}
function buildFlowDiagramMermaidSource(diagram, colorScheme) {
const palette = getModelWeaveMermaidPalette();
const lines = ["flowchart LR"];
const colorClasses = /* @__PURE__ */ new Map();
if (!colorScheme) {
lines.push(
` ${buildModelWeaveMermaidClassDef("screen", palette.dfdProcessFill, palette.dfdProcessBorder, { strokeWidth: 1.5 })}`,
` ${buildModelWeaveMermaidClassDef("process", palette.dfdProcessFill, palette.dfdProcessBorder, { strokeWidth: 1.5 })}`,
` ${buildModelWeaveMermaidClassDef("context", palette.dfdOtherFill, palette.dfdOtherBorder, { strokeWidth: 1.5 })}`,
` ${buildModelWeaveMermaidClassDef("store", palette.dfdDatastoreFill, palette.dfdDatastoreBorder, { strokeWidth: 1.5 })}`,
` ${buildModelWeaveMermaidClassDef("external", palette.dfdExternalFill, palette.dfdExternalBorder, { strokeWidth: 1.5 })}`
);
}
const nodeIds = /* @__PURE__ */ new Map();
const domainStyles = [];
const flowDomains = getFlowDiagramDomains(diagram);
const usesResolvedDomains = flowDomains.length > 0;
const flowDomainsById = new Map(flowDomains.map((domain) => [domain.id, domain]));
const groupedNodes = /* @__PURE__ */ new Map();
const ungroupedNodes = [];
for (const node of diagram.nodes) {
const domainId = getNodeDomainId(node);
if (domainId && flowDomainsById.has(domainId)) {
if (!groupedNodes.has(domainId)) {
groupedNodes.set(domainId, []);
}
groupedNodes.get(domainId).push(node);
} else if (domainId && !usesResolvedDomains) {
const synthetic = createSyntheticDomainEntry(domainId, flowDomainsById.size);
flowDomainsById.set(domainId, synthetic);
groupedNodes.set(domainId, [node]);
} else {
ungroupedNodes.push(node);
}
}
for (const root of buildDomainTree(Array.from(flowDomainsById.values()))) {
appendFlowDiagramDomainSubgraph(
lines,
root,
groupedNodes,
nodeIds,
1,
colorScheme,
colorClasses,
domainStyles
);
}
for (const node of ungroupedNodes) {
appendFlowDiagramNode(lines, node, nodeIds, 1, colorScheme, colorClasses);
}
for (const edge of diagram.edges) {
const from = nodeIds.get(edge.source);
const to = nodeIds.get(edge.target);
if (!from || !to) {
continue;
}
const label = sanitizeMermaidEdgeLabel(edge.label);
if (label) {
lines.push(` ${from} -->|${label}| ${to}`);
} else {
lines.push(` ${from} --> ${to}`);
}
}
if (colorClasses.size > 0) {
lines.push("");
for (const [className, style] of colorClasses) {
lines.push(` classDef ${className} ${formatMermaidClassDefStyle(style)}`);
}
}
if (domainStyles.length > 0) {
lines.push("", ...domainStyles);
}
return lines.join("\n");
}
function appendFlowDiagramDomainSubgraph(lines, domainNode, groupedNodes, nodeIds, depth, colorScheme, colorClasses, domainStyles) {
const childLines = [];
for (const child of domainNode.children) {
appendFlowDiagramDomainSubgraph(
childLines,
child,
groupedNodes,
nodeIds,
depth + 1,
colorScheme,
colorClasses,
domainStyles
);
}
const nodes = groupedNodes.get(domainNode.domain.id) ?? [];
if (nodes.length === 0 && childLines.length === 0) {
return false;
}
const indent = " ".repeat(depth);
const domainMermaidId = toFlowMermaidDomainId(domainNode.domain.id);
lines.push(`${indent}subgraph ${domainMermaidId}["${buildFlowDomainLabel(domainNode.domain)}"]`);
lines.push(...childLines);
for (const node of nodes) {
appendFlowDiagramNode(lines, node, nodeIds, depth + 1, colorScheme, colorClasses);
}
lines.push(`${indent}end`);
if (colorScheme) {
domainStyles.push(
` style ${domainMermaidId} ${formatMermaidClassDefStyle(
resolveColorStyle(colorScheme, "domain", getFlowDomainColorKind(domainNode.domain))
)}`
);
}
return true;
}
function appendFlowDiagramNode(lines, node, nodeIds, depth, colorScheme, colorClasses) {
const mermaidId = toMermaidNodeId(node.id);
const shape = toFlowDiagramMermaidShape(node.kind);
const className = colorScheme ? registerFlowDiagramColorClass(node.kind, colorScheme, colorClasses) : toFlowDiagramClassName(node.kind);
const indent = " ".repeat(depth);
nodeIds.set(node.id, mermaidId);
lines.push(`${indent}${mermaidId}@{ shape: ${shape}, label: "${escapeMermaidLabel2(node.label ?? node.ref ?? node.id)}" }`);
lines.push(`${indent}class ${mermaidId} ${className}`);
}
function getFlowDiagramDomains(diagram) {
return getOptionalResolvedDomains(diagram);
}
function getOptionalResolvedDomains(diagram) {
const maybeDomains = diagram.diagram.domains;
return Array.isArray(maybeDomains) ? maybeDomains.filter(isDomainEntry) : [];
}
function isDomainEntry(value) {
return Boolean(value && typeof value === "object" && "id" in value && typeof value.id === "string");
}
function createSyntheticDomainEntry(id, rowIndex) {
return { id, name: id, kind: id, rowIndex };
}
function toFlowMermaidDomainId(value) {
return `DOMAIN_${toMermaidNodeId(value)}`;
}
function buildFlowDomainLabel(domain) {
return escapeMermaidLabel2(domain.name?.trim() || domain.id);
}
function getFlowDomainColorKind(domain) {
return domain.kind?.trim() || domain.id;
}
function getDfdMermaidColorSchemeTargets(diagram) {
if (isFlowDiagramModel(diagram.diagram)) {
return hasNodesWithDomain(diagram) ? ["flow_diagram", "domain"] : ["flow_diagram"];
}
if (isDfdDiagramModel(diagram.diagram)) {
return hasNodesWithDomain(diagram) || (diagram.diagram.domains?.length ?? 0) > 0 ? ["dfd", "domain"] : ["dfd"];
}
return [];
}
function hasNodesWithDomain(diagram) {
return diagram.nodes.some((node) => Boolean(getNodeDomainId(node)));
}
function toFlowDiagramMermaidShape(kind) {
switch (kind) {
case "screen":
return "curv-trap";
case "session":
case "store":
case "datastore":
return "lin-cyl";
case "process":
case "app_process":
case "context":
case "work_object":
case "actor":
case "user":
case "message":
case "data":
case "api":
case "service":
case "handler":
case "external":
case "unknown":
default:
return "rect";
}
}
function registerFlowDiagramColorClass(kind, colorScheme, colorClasses) {
const className = toFlowDiagramColorClassName(kind);
colorClasses?.set(
className,
resolveColorStyle(colorScheme, "flow_diagram", typeof kind === "string" ? kind : void 0)
);
return className;
}
function toFlowDiagramColorClassName(kind) {
const suffix = typeof kind === "string" && kind.trim() ? kind.trim() : "default";
return `kind_flow_diagram_${sanitizeMermaidId(suffix)}`;
}
function toFlowDiagramClassName(kind) {
switch (kind) {
case "screen":
return "screen";
case "session":
case "store":
case "datastore":
return "store";
case "context":
case "work_object":
case "message":
return "context";
case "process":
case "app_process":
case "data":
case "api":
case "service":
case "handler":
return "process";
case "external":
case "actor":
case "user":
return "external";
case "unknown":
default:
return "process";
}
}
function appendDfdDomainSubgraph(lines, domainNode, groupedNodes, nodeIds, depth, colorScheme, colorClasses, domainStyles) {
const childLines = [];
for (const child of domainNode.children) {
appendDfdDomainSubgraph(
childLines,
child,
groupedNodes,
nodeIds,
depth + 1,
colorScheme,
colorClasses,
domainStyles
);
}
const nodes = groupedNodes.get(domainNode.domain.id) ?? [];
if (nodes.length === 0 && childLines.length === 0) {
return false;
}
const indent = " ".repeat(depth);
const domainMermaidId = toMermaidDomainId(domainNode.domain.id);
lines.push(`${indent}subgraph ${domainMermaidId}["${buildDomainLabel(domainNode.domain)}"]`);
lines.push(...childLines);
for (const node of nodes) {
const mermaidId = toMermaidNodeId(node.id);
nodeIds.set(node.id, mermaidId);
lines.push(`${indent} ${mermaidId}${toMermaidNodeDeclaration(
node,
getDfdObject(node),
colorScheme,
colorClasses
)}`);
}
lines.push(`${indent}end`);
if (colorScheme) {
domainStyles.push(
` style ${domainMermaidId} ${formatMermaidClassDefStyle(
resolveColorStyle(colorScheme, "domain", domainNode.domain.kind)
)}`
);
}
return true;
}
function getDfdLocalDomains(diagram) {
return isDfdDiagramModel(diagram.diagram) ? diagram.diagram.domains ?? [] : [];
}
function isDfdDiagramModel(diagram) {
return diagram.schema === "dfd_diagram";
}
function isFlowDiagramModel(diagram) {
return diagram.schema === "flow_diagram";
}
function getDfdObject(node) {
return node.object && typeof node.object === "object" && "fileType" in node.object && node.object.fileType === "dfd-object" ? node.object : void 0;
}
function getNodeDomainId(node) {
const domain = node.metadata?.domain;
return typeof domain === "string" && domain.trim() ? domain.trim() : void 0;
}
function toMermaidDomainId(value) {
return `domain_${toMermaidNodeId(value)}`;
}
function buildDomainLabel(domain) {
const displayName = domain.name?.trim() || domain.id;
const label = domain.kind?.trim() ? `${displayName} [${domain.kind.trim()}]` : displayName;
return escapeMermaidLabel2(label);
}
function createDomainPlacementDetails(diagram, labels) {
if (!isDfdDiagramModel(diagram.diagram)) {
return null;
}
const sources = diagram.diagram.domainSourceSummaries ?? [];
const domainsById = new Map(getDfdLocalDomains(diagram).map((domain) => [domain.id, domain]));
const placed = diagram.nodes.map((node) => ({ node, domainId: getNodeDomainId(node) })).filter(
(entry) => Boolean(entry.domainId)
);
if (sources.length === 0 && placed.length === 0) {
return null;
}
const section = activeDocument.createElement("details");
section.className = "mdspec-section";
section.addClass("model-weave-diagram-details");
section.open = false;
const resolvedCount = placed.filter((entry) => domainsById.has(entry.domainId)).length;
const summary = activeDocument.createElement("summary");
summary.textContent = `${labels?.domainPlacement ?? "Domain placement"} (${resolvedCount}/${placed.length} ${labels?.resolved ?? "resolved"})`;
summary.addClass("model-weave-diagram-details-summary");
section.appendChild(summary);
const list = activeDocument.createElement("ul");
list.addClass("model-weave-diagram-details-list");
for (const source of sources) {
const item = activeDocument.createElement("li");
item.addClass("model-weave-diagram-details-item");
item.textContent = [
modelWeaveText("Source", "Source"),
source.ref.ref,
source.status,
source.resolvedPath ?? "-",
`${source.domainCount} domains`
].join(" / ");
list.appendChild(item);
}
for (const entry of placed) {
const domain = domainsById.get(entry.domainId);
const item = activeDocument.createElement("li");
item.addClass("model-weave-diagram-details-item");
item.textContent = [
modelWeaveText("Object", "Object"),
entry.node.id,
entry.domainId,
domain ? labels?.resolved ?? "resolved" : labels?.unresolved ?? "unresolved"
].join(" / ");
list.appendChild(item);
}
section.appendChild(list);
return section;
}
function createFlowDetails(edges, labels) {
const section = activeDocument.createElement("details");
section.className = "mdspec-section";
section.addClass("model-weave-diagram-details");
section.open = false;
const summary = activeDocument.createElement("summary");
summary.textContent = `${labels?.displayedFlows ?? "Displayed flows"} (${edges.length})`;
summary.addClass("model-weave-diagram-details-summary");
section.appendChild(summary);
if (edges.length === 0) {
const empty = activeDocument.createElement("p");
empty.textContent = labels?.noFlows ?? "No flows are used for rendering.";
empty.addClass("model-weave-diagram-details-empty");
section.appendChild(empty);
return section;
}
const list = activeDocument.createElement("ul");
list.addClass("model-weave-diagram-details-list");
for (const edge of edges) {
const item = activeDocument.createElement("li");
item.addClass("model-weave-diagram-details-item");
const notes = formatDiagramEdgeNotes(edge.metadata?.notes);
item.textContent = `${edge.id ?? "-"} / ${edge.source} -> ${edge.target} / ${edge.label ?? "-"}${notes ? ` / ${notes}` : ""}`;
list.appendChild(item);
}
section.appendChild(list);
return section;
}
function createObjectDetails(diagram, labels) {
const section = activeDocument.createElement("details");
section.className = "mdspec-section";
section.addClass("model-weave-diagram-details");
section.open = false;
const summary = activeDocument.createElement("summary");
summary.textContent = `${labels?.displayedObjects ?? "Displayed objects"} (${diagram.nodes.length})`;
summary.addClass("model-weave-diagram-details-summary");
section.appendChild(summary);
if (diagram.nodes.length === 0) {
const empty = activeDocument.createElement("p");
empty.textContent = labels?.noObjects ?? "No objects are used for rendering.";
empty.addClass("model-weave-diagram-details-empty");
section.appendChild(empty);
return section;
}
const domainsById = new Map(getDfdLocalDomains(diagram).map((domain) => [domain.id, domain]));
const list = activeDocument.createElement("ul");
list.addClass("model-weave-diagram-details-list");
for (const node of diagram.nodes) {
const item = activeDocument.createElement("li");
item.addClass("model-weave-diagram-details-item");
const domainId = getNodeDomainId(node);
const domain = domainId ? domainsById.get(domainId) : void 0;
const domainLabel = domain ? domain.name || domain.id : domainId;
item.textContent = [
node.id,
node.label ?? node.ref ?? "-",
modelWeaveText("Domain", "Domain") + `: ${domainLabel ?? "-"}`
].join(" / ");
list.appendChild(item);
}
section.appendChild(list);
return section;
}
function toMermaidNodeId(value) {
const normalized = value.replace(/[^A-Za-z0-9_]/g, "_");
if (/^[A-Za-z_]/.test(normalized)) {
return normalized;
}
return `N_${normalized}`;
}
function toMermaidNodeDeclaration(node, object, colorScheme, colorClasses) {
const label = escapeMermaidLabel2(node.label ?? object?.name ?? node.ref ?? node.id);
const kind = object?.kind ?? node.kind;
const className = colorScheme ? registerDfdColorClass(kind, colorScheme, colorClasses) : toBuiltInDfdClassName(kind);
switch (kind) {
case "datastore":
return `[("${label}")]:::${className}`;
case "process":
return `["${label}"]:::${className}`;
case "other":
return `["${label}"]:::${className}`;
case "external":
default:
return `["${label}"]:::${className}`;
}
}
function registerDfdColorClass(kind, colorScheme, colorClasses) {
const className = toDfdColorClassName(kind);
colorClasses?.set(className, resolveColorStyle(colorScheme, "dfd", kind));
return className;
}
function toBuiltInDfdClassName(kind) {
switch (kind) {
case "datastore":
return "dfdDatastore";
case "process":
return "dfdProcess";
case "other":
return "dfdOther";
case "external":
default:
return "dfdExternal";
}
}
function toDfdColorClassName(kind) {
const suffix = kind?.trim() ? kind.trim() : "default";
return `kind_dfd_${sanitizeMermaidId(suffix)}`;
}
function formatMermaidClassDefStyle(style) {
return [
style.fill ? `fill:${style.fill}` : void 0,
style.stroke ? `stroke:${style.stroke}` : void 0,
style.text ? `color:${style.text}` : void 0
].filter((entry) => Boolean(entry)).join(",");
}
function escapeMermaidLabel2(value) {
return value.replace(/"/g, '\\"').replace(/\r?\n/g, "
");
}
function sanitizeMermaidEdgeLabel(value) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
if (!trimmed) {
return null;
}
return trimmed.replace(/\|/g, "/").replace(/[[\]()]/g, " ").replace(/\r?\n/g, " ").replace(/\s+/g, " ").trim();
}
function formatDiagramEdgeNotes(notes) {
if (typeof notes === "string") {
return notes.trim();
}
if (Array.isArray(notes)) {
return notes.filter((note) => typeof note === "string" && note.trim().length > 0).join(" / ");
}
if (notes && typeof notes === "object") {
try {
const serialized = JSON.stringify(notes);
return typeof serialized === "string" && serialized !== "{}" ? serialized : "";
} catch {
return "";
}
}
return "";
}
// src/renderers/flow-renderer.ts
function renderFlowDiagram(diagram) {
const root = activeDocument.createElement("section");
root.className = "mdspec-diagram mdspec-diagram--flow";
const title = activeDocument.createElement("h2");
title.textContent = `${diagram.diagram.name} (flow)`;
root.appendChild(title);
const list = activeDocument.createElement("ol");
list.className = "mdspec-flow";
for (const node of diagram.nodes) {
const item = activeDocument.createElement("li");
item.textContent = getNodeLabel2(node);
list.appendChild(item);
}
if (list.childElementCount === 0) {
const empty = activeDocument.createElement("p");
empty.textContent = "No objects referenced.";
root.appendChild(empty);
} else {
root.appendChild(list);
}
if (diagram.edges.length > 0) {
const relations = activeDocument.createElement("ul");
for (const edge of diagram.edges) {
const item = activeDocument.createElement("li");
item.textContent = `${edge.source} -> ${edge.target}${edge.label ? ` (${edge.label})` : ""}`;
relations.appendChild(item);
}
root.appendChild(relations);
}
return root;
}
function getNodeLabel2(node) {
if (!node.object) {
return node.ref ?? node.id;
}
return node.object.fileType === "er-entity" ? node.object.logicalName : node.object.name;
}
// src/renderers/diagram-renderer.ts
function renderDiagramModel(diagram, options) {
if (diagram.diagram.schema === "flow_diagram") {
return renderDfdMermaidDiagram(diagram, options);
}
switch (diagram.diagram.kind) {
case "class":
return renderClassDiagramByMode(diagram, options);
case "er":
return renderErDiagramByMode(diagram, options);
case "dfd":
return renderDfdMermaidDiagram(diagram, options);
case "flow":
return renderFlowDiagram(diagram);
case "component":
return renderComponentDiagram(diagram);
default:
return createReservedKindFallback(diagram.diagram.kind);
}
}
function renderClassDiagramByMode(diagram, options) {
const mode = options?.renderMode;
if (mode === "mermaid-detail") {
return renderClassMermaidDetailDiagram(diagram, options);
}
if (mode === "mermaid") {
return renderClassMermaidDiagram(diagram, options);
}
return renderClassDiagram(diagram, options);
}
function renderErDiagramByMode(diagram, options) {
const mode = options?.renderMode;
if (mode === "mermaid-detail") {
return renderErMermaidDetailDiagram(diagram, options);
}
if (mode === "mermaid") {
return renderErMermaidDiagram(diagram, options);
}
return renderErDiagram(diagram, options);
}
function createReservedKindFallback(kind) {
const root = activeDocument.createElement("section");
root.className = "mdspec-fallback";
const title = activeDocument.createElement("h2");
title.textContent = modelWeaveText(
"Diagram preview is not available",
"Diagram preview \u306F\u5229\u7528\u3067\u304D\u307E\u305B\u3093"
);
const message = activeDocument.createElement("p");
message.textContent = modelWeaveText(
`Reserved diagram kind "${kind}" is not rendered in v1.`,
`\u4E88\u7D04\u6E08\u307F diagram kind "${kind}" \u306F v1 \u3067\u306F\u63CF\u753B\u3055\u308C\u307E\u305B\u3093\u3002`
);
root.append(title, message);
return root;
}
// src/core/flow-diagram-view-mode.ts
function normalizeFlowDiagramViewMode(value) {
if (typeof value !== "string") {
return void 0;
}
const normalized = value.trim().toLowerCase();
return normalized === "detail" || normalized === "screen" ? normalized : void 0;
}
function resolveInitialFlowDiagramViewMode(diagram, defaultViewMode) {
return diagram.flowViewSpecified ? diagram.flowView : normalizeFlowDiagramViewMode(defaultViewMode) ?? "detail";
}
function buildFlowDiagramViewModeInitializationKey(diagram, defaultViewMode) {
if (diagram.flowViewSpecified) {
return `frontmatter:${diagram.flowView}`;
}
const defaultMode = normalizeFlowDiagramViewMode(defaultViewMode) ?? "detail";
const rawValue = typeof diagram.flowViewRaw === "string" ? diagram.flowViewRaw.trim() : "";
return rawValue ? `invalid:${rawValue}:settings:${defaultMode}` : `settings:${defaultMode}`;
}
var FlowDiagramViewModeState = class {
constructor() {
this.entriesByFilePath = /* @__PURE__ */ new Map();
}
synchronize(filePath, diagram, defaultViewMode) {
const initializationKey = buildFlowDiagramViewModeInitializationKey(diagram, defaultViewMode);
const existing = this.entriesByFilePath.get(filePath);
if (existing?.initializationKey === initializationKey) {
return { mode: existing.mode, initializationChanged: false };
}
const mode = resolveInitialFlowDiagramViewMode(diagram, defaultViewMode);
this.entriesByFilePath.set(filePath, { mode, initializationKey });
return { mode, initializationChanged: true };
}
getOrInitialize(filePath, diagram, defaultViewMode) {
return this.synchronize(filePath, diagram, defaultViewMode).mode;
}
set(filePath, mode) {
const existing = this.entriesByFilePath.get(filePath);
if (!existing) {
return;
}
existing.mode = mode;
}
};
// src/core/app-process-step-interaction-target.ts
function resolveAppProcessStepInteractionTarget(model, step, context) {
const index = context.index ?? null;
if (index) {
for (const source of getStepReferencePriority(step.kind)) {
const resolved = resolveStepCandidate(model, step, source, index);
if (resolved) {
return resolved;
}
}
}
return {
stepId: step.id,
source: "fallback",
rawValue: context.sourcePath,
targetRef: context.sourcePath,
targetPath: context.sourcePath,
targetModelType: "app-process"
};
}
function resolveStepCandidate(model, step, source, index) {
switch (source) {
case "screen":
return resolveDirectCandidate(step, source, step.screen, index);
case "invoke":
return resolveDirectCandidate(step, source, step.invoke, index);
case "rule":
return resolveDirectCandidate(step, source, step.rule, index);
case "input":
return resolveInputCandidate(model.inputs, step, index);
case "output":
return resolveOutputCandidate(model.outputs, step, index);
}
}
function resolveInputCandidate(inputs, step, index) {
const raw = step.input?.trim();
if (!raw) {
return null;
}
const direct = resolveCandidateReference(raw, index);
if (direct) {
return toInteractionTarget(step, "input", raw, direct);
}
if (isDirectReferenceSyntax(raw)) {
return null;
}
const input = inputs.find((entry) => entry.id.trim() === raw);
if (!input) {
return null;
}
const data = resolveOptionalReference(input.data, index);
if (data) {
return toInteractionTarget(step, "input", data.raw, data.resolved);
}
const source = resolveOptionalReference(input.source, index);
return source ? toInteractionTarget(step, "input", source.raw, source.resolved) : null;
}
function resolveOutputCandidate(outputs, step, index) {
const raw = step.output?.trim();
if (!raw) {
return null;
}
const direct = resolveCandidateReference(raw, index);
if (direct) {
return toInteractionTarget(step, "output", raw, direct);
}
if (isDirectReferenceSyntax(raw)) {
return null;
}
const output = outputs.find((entry) => entry.id.trim() === raw);
if (!output) {
return null;
}
const data = resolveOptionalReference(output.data, index);
if (data) {
return toInteractionTarget(step, "output", data.raw, data.resolved);
}
const target = resolveOptionalReference(output.target, index);
return target ? toInteractionTarget(step, "output", target.raw, target.resolved) : null;
}
function resolveDirectCandidate(step, source, rawValue, index) {
const raw = rawValue?.trim();
if (!raw) {
return null;
}
const resolved = resolveCandidateReference(raw, index);
return resolved ? toInteractionTarget(step, source, raw, resolved) : null;
}
function resolveOptionalReference(rawValue, index) {
const raw = rawValue?.trim();
if (!raw) {
return null;
}
const resolved = resolveCandidateReference(raw, index);
return resolved ? { raw, resolved } : null;
}
function resolveCandidateReference(raw, index) {
const resolved = resolveReferenceIdentity(raw, index);
return resolved.resolvedFile ? resolved : null;
}
function toInteractionTarget(step, source, rawValue, resolved) {
return {
stepId: step.id,
source,
rawValue,
targetRef: resolved.target ?? rawValue,
targetPath: resolved.resolvedFile,
targetId: resolved.resolvedId,
targetModelType: resolved.resolvedModelType
};
}
function isDirectReferenceSyntax(raw) {
const parsed = parseReferenceValue(raw);
return Boolean(parsed && parsed.kind !== "raw");
}
function getStepReferencePriority(kind) {
switch (normalizeStepKindForPriority(kind)) {
case "screen":
return ["screen", "output", "input", "rule", "invoke"];
case "subflow":
case "flow":
return ["invoke", "screen", "rule", "input", "output"];
case "decision":
return ["rule", "input", "output", "screen", "invoke"];
case "input":
case "event":
return ["input", "screen", "rule", "invoke", "output"];
case "data":
return ["input", "output", "screen", "rule", "invoke"];
case "store":
return ["output", "input", "screen", "rule", "invoke"];
case "api":
case "batch":
case "message":
case "external":
return ["invoke", "screen", "input", "output", "rule"];
case "error":
case "end":
return ["output", "screen", "rule", "input", "invoke"];
case "wait":
case "connector":
return ["invoke", "screen", "rule", "input", "output"];
case "process":
default:
return ["invoke", "screen", "rule", "input", "output"];
}
}
function normalizeStepKindForPriority(kind) {
const normalized = kind?.trim().toLowerCase();
switch (normalized) {
case "screen":
case "subflow":
case "flow":
case "decision":
case "input":
case "event":
case "data":
case "store":
case "api":
case "batch":
case "message":
case "external":
case "error":
case "end":
case "wait":
case "connector":
case "process":
return normalized;
default:
return "process";
}
}
// src/utils/display-text.ts
var DISPLAY_TEXT_ESCAPES = {
"|": "|",
"[": "[",
"]": "]",
'"': '"',
"\\": "\\"
};
function decodeEscapedDisplayText(value) {
if (!value) {
return "";
}
return value.replace(
/\\([|[\]"\\])/g,
(match, escaped) => DISPLAY_TEXT_ESCAPES[escaped] ?? match
);
}
// src/renderers/app-process-business-flow.ts
function renderAppProcessBusinessFlow(model, options = {}) {
const shell3 = createMermaidShell({
className: "model-weave-app-process-business-flow",
title: `${model.title} (app_process / business flow)`,
forExport: options.forExport,
onExportPng: options.onExportPng,
onExportAndOpenPng: options.onExportAndOpenPng,
exportPngLabel: options.exportPngLabel,
exportPngTitle: options.exportPngTitle,
exportAndOpenPngLabel: options.exportAndOpenPngLabel,
exportAndOpenPngTitle: options.exportAndOpenPngTitle
});
const sourcePath = options.interactionSourcePath ?? "";
const interactionTargets = buildAppProcessBusinessFlowInteractionTargets(
model,
sourcePath,
options.interactionIndex
);
const source = buildAppProcessBusinessFlowMermaidSource(
model,
options.colorScheme,
options.flowDirection
);
const ready = renderMermaidSourceIntoShell(shell3, {
source,
renderIdPrefix: "model_weave_app_process_flow",
fitHorizontalAlign: "left",
fitVerticalAlign: "top",
minZoom: 0.02,
minFitScale: 0.03,
viewportState: options.viewportState,
onViewportStateChange: options.onViewportStateChange,
showSourcePanel: !options.forExport,
sourcePanelContainer: options.sourcePanelContainer ?? shell3.root,
sourcePanelPlacement: options.sourcePanelPlacement,
sourcePanelTitle: options.sourcePanelTitle,
sourcePanelCopyLabel: options.sourcePanelCopyLabel,
showRenderDebug: !options.forExport && options.debug !== false && options.showMermaidRenderDebug === true
}).then(() => {
if (!options.forExport && options.app && interactionTargets.length > 0) {
attachMermaidNodeInteractions({
app: options.app,
rootEl: shell3.surface,
targets: interactionTargets,
source: "model-weave",
nodeClassName: "model-weave-mermaid-interactive-node",
dragThreshold: 6,
isDebugEnabled: () => options.showMermaidRenderDebug === true,
debugName: "App Process Business Flow",
formatTitle: (target) => target.label ? `${target.label} (${target.targetType ?? "model"})` : target.linktext,
openLinkText: options.onStepNodeClick ? (target, event) => {
const stepId = target.nodeId ?? target.modelId;
if (!stepId) {
return;
}
return options.onStepNodeClick?.(stepId, event);
} : void 0
});
}
}).catch((error) => {
shell3.root.addClass("model-weave-mermaid-fallback-shell");
shell3.canvas.replaceChildren(
createMermaidFallbackNotice(
modelWeaveText(
"Business Flow Mermaid preview could not be rendered. Use the summary tables below.",
"Business Flow Mermaid \u30D7\u30EC\u30D3\u30E5\u30FC\u3092\u63CF\u753B\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u4E0B\u306E\u30B5\u30DE\u30EA\u30C6\u30FC\u30D6\u30EB\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
)
)
);
});
setMermaidRenderReadyPromise(shell3.root, ready);
return shell3.root;
}
function buildAppProcessBusinessFlowInteractionTargets(model, sourcePath, index) {
if (!sourcePath) {
return [];
}
return model.steps.map((step, stepIndex) => {
const target = resolveAppProcessStepInteractionTarget(
{
inputs: model.inputs ?? [],
outputs: model.outputs ?? []
},
step,
{
index,
sourcePath
}
);
const targetPath = target.targetPath ?? sourcePath;
return {
mermaidId: `S${stepIndex + 1}`,
linktext: targetPath,
sourcePath,
label: getStepLabel(step),
kind: `app-process-step-${target.source}`,
targetType: target.targetModelType ?? "app_process",
filePath: targetPath,
modelId: target.targetId ?? step.id,
modelType: target.targetModelType ?? "app-process",
nodeId: step.id,
status: target.source
};
});
}
function buildAppProcessBusinessFlowMermaidSource(model, colorScheme, flowDirection) {
const stepNodeIds = /* @__PURE__ */ new Map();
const stepNodeIdsByStepId = /* @__PURE__ */ new Map();
model.steps.forEach((step, index) => {
const nodeId = `S${index + 1}`;
stepNodeIds.set(step, nodeId);
if (step.id) {
stepNodeIdsByStepId.set(step.id, nodeId);
}
});
const normalizedDirection = normalizeAppProcessBusinessFlowDirectionWithFallback(flowDirection);
const lines = [`flowchart ${normalizedDirection}`];
const colorClasses = /* @__PURE__ */ new Map();
const domainStyles = [];
const nodeClasses = [];
const localDomains = model.domains ?? [];
const localDomainsById = new Map(localDomains.map((domain) => [domain.id, domain]));
const domainStepGroups = /* @__PURE__ */ new Map();
const flatPlacementGroups = /* @__PURE__ */ new Map();
const ungrouped = [];
for (const step of model.steps) {
const domainId = step.domain?.trim();
if (domainId && localDomainsById.has(domainId)) {
const group2 = domainStepGroups.get(domainId) ?? [];
group2.push(step);
domainStepGroups.set(domainId, group2);
continue;
}
const flatPlacement = getStepFlatPlacementGroup(step);
if (!flatPlacement) {
ungrouped.push(step);
continue;
}
const group = flatPlacementGroups.get(flatPlacement) ?? [];
group.push(step);
flatPlacementGroups.set(flatPlacement, group);
}
const includedDomains = getIncludedDomains(localDomains, domainStepGroups);
for (const root of buildDomainTree(includedDomains)) {
appendAppProcessDomainSubgraph(
lines,
root,
domainStepGroups,
stepNodeIds,
nodeClasses,
colorClasses,
domainStyles,
colorScheme,
1
);
}
let groupIndex = 0;
for (const [placement, steps] of flatPlacementGroups) {
groupIndex += 1;
lines.push(` subgraph L${groupIndex}["${escapeMermaidLabel(placement)}"]`);
for (const step of steps) {
lines.push(` ${buildStepNodeDeclaration(stepNodeIds.get(step), step)}`);
appendStepColorClass(
nodeClasses,
colorClasses,
stepNodeIds.get(step),
step,
colorScheme
);
}
lines.push(" end");
}
for (const step of ungrouped) {
lines.push(` ${buildStepNodeDeclaration(stepNodeIds.get(step), step)}`);
appendStepColorClass(
nodeClasses,
colorClasses,
stepNodeIds.get(step),
step,
colorScheme
);
}
const explicitEdges = model.hasExplicitFlows ? buildExplicitFlowEdges(model.flows, stepNodeIdsByStepId) : [];
const implicitEdges = buildImplicitStepOrderEdges(
model.steps,
stepNodeIds,
getExplicitFlowSourceStepIds(model.flows, stepNodeIdsByStepId)
);
const edges = [...implicitEdges, ...explicitEdges];
for (const edge of edges) {
const { fromId, toId } = edge;
if (!fromId || !toId) {
continue;
}
lines.push(
edge.label ? ` ${fromId} -->|${toMermaidQuotedLabel(edge.label)}| ${toId}` : ` ${fromId} --> ${toId}`
);
}
if (colorClasses.size > 0) {
lines.push("");
for (const [className, style] of colorClasses) {
lines.push(` classDef ${className} ${formatMermaidClassDefStyle2(style)}`);
}
lines.push("", ...nodeClasses);
}
if (domainStyles.length > 0) {
lines.push("", ...domainStyles);
}
return lines.join("\n");
}
function getAppProcessBusinessFlowColorSchemeTargets(model) {
const targets = ["app_process"];
if (hasResolvedAppProcessDomainGroups(model)) {
targets.push("domain");
}
return targets;
}
function hasResolvedAppProcessDomainGroups(model) {
const domainIds = new Set(
(model.domains ?? []).map((domain) => domain.id.trim()).filter(Boolean)
);
if (domainIds.size === 0) {
return false;
}
return model.steps.some((step) => {
const domainId = step.domain?.trim();
return Boolean(domainId && domainIds.has(domainId));
});
}
function getIncludedDomains(localDomains, domainStepGroups) {
const domainsById = new Map(localDomains.map((domain) => [domain.id, domain]));
const includedIds = /* @__PURE__ */ new Set();
for (const domainId of domainStepGroups.keys()) {
let current = domainsById.get(domainId);
const seen = /* @__PURE__ */ new Set();
while (current && !seen.has(current.id)) {
includedIds.add(current.id);
seen.add(current.id);
current = current.parent ? domainsById.get(current.parent) : void 0;
}
}
return localDomains.filter((domain) => includedIds.has(domain.id));
}
function appendAppProcessDomainSubgraph(lines, domainNode, domainStepGroups, stepNodeIds, nodeClasses, colorClasses, domainStyles, colorScheme, depth) {
const childLines = [];
for (const child of domainNode.children) {
appendAppProcessDomainSubgraph(
childLines,
child,
domainStepGroups,
stepNodeIds,
nodeClasses,
colorClasses,
domainStyles,
colorScheme,
depth + 1
);
}
const steps = domainStepGroups.get(domainNode.domain.id) ?? [];
if (steps.length === 0 && childLines.length === 0) {
return false;
}
const indent = " ".repeat(depth);
lines.push(`${indent}subgraph ${toAppProcessDomainSubgraphId(domainNode.domain.id)}["${buildAppProcessDomainLabel(domainNode.domain)}"]`);
lines.push(...childLines);
for (const step of steps) {
lines.push(`${indent} ${buildStepNodeDeclaration(stepNodeIds.get(step), step)}`);
appendStepColorClass(
nodeClasses,
colorClasses,
stepNodeIds.get(step),
step,
colorScheme
);
}
lines.push(`${indent}end`);
if (colorScheme) {
domainStyles.push(
` style ${toAppProcessDomainSubgraphId(domainNode.domain.id)} ${formatMermaidClassDefStyle2(
resolveColorStyle(colorScheme, "domain", domainNode.domain.kind)
)}`
);
}
return true;
}
function toAppProcessDomainSubgraphId(domainId) {
return `domain_${sanitizeMermaidId(domainId)}`;
}
function buildAppProcessDomainLabel(domain) {
return escapeMermaidLabel(domain.name?.trim() || domain.id);
}
function getStepFlatPlacementGroup(step) {
const domain = step.domain?.trim();
if (domain) {
return domain;
}
const lane = step.lane?.trim();
return lane || null;
}
function appendStepColorClass(nodeClasses, colorClasses, nodeId, step, colorScheme) {
if (!colorScheme || !nodeId) {
return;
}
const className = toAppProcessColorClassName(step.kind);
colorClasses.set(
className,
resolveColorStyle(colorScheme, "app_process", step.kind)
);
nodeClasses.push(` class ${nodeId} ${className}`);
}
function toAppProcessColorClassName(kind) {
const suffix = kind?.trim() ? kind.trim() : "default";
return `kind_app_process_${sanitizeMermaidId(suffix)}`;
}
function formatMermaidClassDefStyle2(style) {
return [
style.fill ? `fill:${style.fill}` : void 0,
style.stroke ? `stroke:${style.stroke}` : void 0,
style.text ? `color:${style.text}` : void 0
].filter((entry) => Boolean(entry)).join(",");
}
function buildExplicitFlowEdges(flows, stepNodeIdsByStepId) {
return flows.map((flow) => ({
fromId: stepNodeIdsByStepId.get(flow.from),
toId: stepNodeIdsByStepId.get(flow.to),
label: getFlowLabel(flow)
})).filter(
(edge) => Boolean(edge.fromId && edge.toId)
);
}
function buildImplicitStepOrderEdges(steps, stepNodeIds, suppressedSourceStepIds) {
return steps.slice(0, -1).filter((step) => !suppressedSourceStepIds.has(step.id)).map((step, index) => ({
fromId: stepNodeIds.get(step),
toId: stepNodeIds.get(getNextStep(steps, step)),
label: ""
})).filter(
(edge) => Boolean(edge.fromId && edge.toId)
);
}
function getExplicitFlowSourceStepIds(flows, stepNodeIdsByStepId) {
return new Set(
flows.filter(
(flow) => Boolean(
flow.from && flow.to && stepNodeIdsByStepId.has(flow.from) && stepNodeIdsByStepId.has(flow.to)
)
).map((flow) => flow.from)
);
}
function getNextStep(steps, step) {
const index = steps.indexOf(step);
return index >= 0 ? steps[index + 1] : void 0;
}
function getStepLabel(step) {
return decodeEscapedDisplayText(step.label?.trim()) || step.id || "(step)";
}
function buildStepNodeDeclaration(nodeId, step) {
const id = nodeId ?? "S";
const label = toMermaidQuotedLabel(getStepLabel(step));
switch (getStepShapeKind(step)) {
case "terminal":
return `${id}([${label}])`;
case "decision":
return `${id}{${label}}`;
case "input":
return `${id}[/${label}/]`;
case "subflow":
return `${id}[[${label}]]`;
case "database":
return `${id}[(${label})]`;
case "connector":
return `${id}((${label}))`;
case "rounded-process":
return `${id}(${label})`;
case "process":
default:
return `${id}[${label}]`;
}
}
function normalizeAppProcessStepKind(kind) {
const normalized = kind?.trim().toLowerCase();
switch (normalized) {
case "start":
case "process":
case "decision":
case "input":
case "screen":
case "flow":
case "subflow":
case "end":
case "event":
case "api":
case "batch":
case "message":
case "data":
case "store":
case "wait":
case "error":
case "connector":
case "external":
return normalized;
default:
return null;
}
}
function getStepShapeKind(step) {
switch (normalizeAppProcessStepKind(step.kind)) {
case "start":
case "end":
case "event":
case "error":
return "terminal";
case "decision":
return "decision";
case "input":
case "screen":
return "input";
case "flow":
case "subflow":
return "subflow";
case "data":
case "store":
return "database";
case "connector":
return "connector";
case "api":
case "batch":
case "message":
case "wait":
case "external":
return "rounded-process";
case "process":
default:
return "process";
}
}
function getFlowLabel(flow) {
const label = flow.label?.trim();
if (label) {
return decodeEscapedDisplayText(label);
}
return decodeEscapedDisplayText(formatConditionLabel(flow.condition) ?? "");
}
function formatConditionLabel(condition) {
const trimmed = condition?.trim();
if (!trimmed) {
return null;
}
const formatted = trimmed.replace(
/\[\[([^\]]+)\]\](\.\s*[A-Za-z0-9_-]+)?/g,
(match, inner, suffix) => {
const display = formatReferenceDisplayLabel(`[[${inner}]]`);
if (!display) {
return match;
}
return `${display}${suffix ? suffix.replace(/\s+/g, "") : ""}`;
}
);
if (formatted !== trimmed) {
return formatted;
}
return formatReferenceDisplayLabel(trimmed) ?? trimmed;
}
function formatReferenceDisplayLabel(reference) {
const parsed = parseReferenceValue(reference);
if (!parsed || parsed.kind === "raw") {
return null;
}
const display = parsed.display?.trim();
if (display) {
return display;
}
const target = parsed.target?.trim();
if (!target) {
return null;
}
return target.replace(/\\/g, "/").split("/").filter(Boolean).pop()?.replace(/\.md$/i, "") ?? null;
}
// src/renderers/object-context-renderer.ts
function renderObjectContext(context, options) {
const root = activeDocument.createElement("section");
root.addClass("model-weave-object-context");
root.addClass("model-weave-preview-section");
const titleRow = activeDocument.createElement("div");
titleRow.addClass("model-weave-object-context-title-row");
const title = activeDocument.createElement("h3");
title.textContent = options?.labels?.title ?? "Related objects";
title.addClass("model-weave-object-context-title");
title.addClass("model-weave-preview-section-title");
titleRow.appendChild(title);
const count = activeDocument.createElement("span");
count.textContent = options?.labels?.linked(context.relatedObjects.length) ?? `${context.relatedObjects.length} linked`;
count.addClass("model-weave-object-context-count");
titleRow.appendChild(count);
root.appendChild(titleRow);
root.appendChild(createMiniGraph(context, options));
root.appendChild(createRelatedList(context, options));
return root;
}
function createMiniGraph(context, options) {
const subgraph = buildObjectSubgraphScene(context);
const graph = renderDiagramModel(subgraph, {
onOpenObject: options?.onOpenObject,
app: options?.app,
interactionSourcePath: options?.interactionSourcePath ?? context.object.path,
hideTitle: true,
hideDetails: true,
fitVerticalAlign: options?.fitVerticalAlign ?? "top",
viewportState: options?.viewportState,
onViewportStateChange: options?.onViewportStateChange
});
graph.addClass("model-weave-object-context-graph");
return graph;
}
function createRelatedList(context, options) {
const sortedEntries = [...context.relatedObjects].sort(
(left, right) => compareRelatedEntries(left, right)
);
const details = activeDocument.createElement("details");
details.addClass("model-weave-object-context-list");
details.addClass("model-weave-preview-section");
const summary = activeDocument.createElement("summary");
summary.textContent = context.object.fileType === "er-entity" ? `${options?.labels?.relationDetails ?? "Relation details"} (${sortedEntries.length})` : `${options?.labels?.connectionDetails ?? "Connection details"} (${sortedEntries.length})`;
summary.addClass("model-weave-object-context-summary");
summary.addClass("model-weave-preview-section-title");
details.appendChild(summary);
const tableWrap = activeDocument.createElement("div");
tableWrap.addClass("model-weave-object-context-table-wrap");
tableWrap.addClass("model-weave-table-wrap");
if (sortedEntries.length === 0) {
const empty = activeDocument.createElement("p");
empty.textContent = options?.labels?.noDirectlyRelated ?? "No directly related objects.";
empty.addClass("model-weave-object-context-empty");
details.appendChild(empty);
return details;
}
const table = activeDocument.createElement("table");
table.addClass("model-weave-object-context-table");
table.addClass("model-weave-data-table");
const headers = context.object.fileType === "er-entity" ? ["Related", "Direction", "Relation ID", "Source", "Target", "Kind", "Cardinality", "Mappings", "Notes"] : ["Related", "Direction", "Relation ID", "Source", "Target", "Kind", "Label", "Multiplicity", "Notes"];
const thead = activeDocument.createElement("thead");
const headRow = activeDocument.createElement("tr");
for (const header of headers) {
const cell = activeDocument.createElement("th");
cell.textContent = header;
cell.addClass("model-weave-object-context-th");
headRow.appendChild(cell);
}
thead.appendChild(headRow);
table.appendChild(thead);
const tbody = activeDocument.createElement("tbody");
for (const entry of sortedEntries) {
const row = activeDocument.createElement("tr");
const values = context.object.fileType === "er-entity" ? buildErListRow(entry) : buildClassListRow(entry);
values.forEach((value, index) => {
const cell = activeDocument.createElement("td");
cell.addClass("model-weave-object-context-td");
if (index === 0 && options?.onOpenObject) {
const wrapper = activeDocument.createElement("div");
wrapper.addClass("model-weave-object-context-link-wrap");
const badge = createDirectionBadge(entry.direction);
wrapper.appendChild(badge);
const button = activeDocument.createElement("button");
button.type = "button";
button.textContent = value;
button.addClass("model-weave-object-context-link");
button.addEventListener("click", () => {
options.onOpenObject?.(entry.relatedObjectId, { openInNewLeaf: false });
});
attachRelatedObjectHoverPreview(button, context, entry, options);
wrapper.appendChild(button);
cell.appendChild(wrapper);
} else if (index === 1) {
cell.appendChild(createDirectionBadge(entry.direction));
} else if (index === 5) {
cell.appendChild(createKindBadge(value));
} else {
cell.textContent = value;
}
row.appendChild(cell);
});
tbody.appendChild(row);
}
table.appendChild(tbody);
tableWrap.appendChild(table);
details.appendChild(tableWrap);
return details;
}
function attachRelatedObjectHoverPreview(element, context, entry, options) {
if (!options?.app || !entry.relatedObject?.path) {
return;
}
const target = createRelatedObjectInteractionTarget(
context,
entry,
options.interactionSourcePath ?? context.object.path
);
if (!target) {
return;
}
attachGraphElementHoverPreview({
app: options.app,
targetEl: element,
target,
source: "model-weave"
});
}
function createRelatedObjectInteractionTarget(context, entry, sourcePath) {
const related = entry.relatedObject;
if (!related?.path) {
return null;
}
const isEr = related.fileType === "er-entity";
const label = isEr ? related.logicalName || related.physicalName || entry.relatedObjectId : related.name || entry.relatedObjectId;
const targetType = isEr ? "er_entity" : "class";
return {
mermaidId: `related:${entry.relatedObjectId}`,
linktext: related.path,
sourcePath,
label,
kind: isEr ? "er-reference" : "class-reference",
targetType,
filePath: related.path,
modelId: entry.relatedObjectId,
modelType: related.fileType
};
}
function buildErListRow(entry) {
const relation = entry.relation;
const related = entry.relatedObject;
const relatedName = related && related.fileType === "er-entity" ? `${related.logicalName} / ${related.physicalName}` : entry.relatedObjectId;
if (!("domain" in relation) || relation.domain !== "er") {
const notes = "notes" in relation && typeof relation.notes === "string" && relation.notes.trim() ? relation.notes : "metadata" in relation && typeof relation.metadata?.notes === "string" ? relation.metadata.notes : "-";
return [
relatedName,
formatDirection(entry.direction),
relation.id || "-",
relation.source,
relation.target,
relation.kind ?? "-",
"-",
"-",
notes
];
}
const mappingSummary = relation.mappings.map((mapping) => `${mapping.localColumn} -> ${mapping.targetColumn}`).join(", ");
return [
relatedName,
formatDirection(entry.direction),
relation.id || "-",
relation.sourceEntity,
relation.targetEntity,
relation.kind,
relation.cardinality ?? "-",
truncateValue(mappingSummary || "-", 72),
relation.notes || "-"
];
}
function buildClassListRow(entry) {
const relation = normalizeClassRelation2(entry.relation);
const relatedName = entry.relatedObject?.fileType === "object" ? entry.relatedObject.name : entry.relatedObjectId;
const multiplicity = [
relation.fromMultiplicity ? `from ${relation.fromMultiplicity}` : "",
relation.toMultiplicity ? `to ${relation.toMultiplicity}` : ""
].filter(Boolean).join(" / ");
return [
relatedName,
formatDirection(entry.direction),
relation.id || "-",
relation.sourceClass,
relation.targetClass,
relation.kind,
relation.label ?? "-",
multiplicity || "-",
relation.notes || "-"
];
}
function normalizeClassRelation2(relation) {
if ("domain" in relation) {
if (relation.domain === "class") {
return relation;
}
return toClassRelationEdge({
id: relation.id,
kind: "association",
source: relation.source,
target: relation.target,
label: relation.label
});
}
return toClassRelationEdge(relation);
}
function compareRelatedEntries(left, right) {
if (left.direction !== right.direction) {
return left.direction === "outgoing" ? -1 : 1;
}
const leftName = getStableRelatedName(left).toLowerCase();
const rightName = getStableRelatedName(right).toLowerCase();
if (leftName !== rightName) {
return leftName.localeCompare(rightName);
}
const leftId = getRelationId(left).toLowerCase();
const rightId = getRelationId(right).toLowerCase();
return leftId.localeCompare(rightId);
}
function getStableRelatedName(entry) {
if (!entry.relatedObject) {
return entry.relatedObjectId;
}
if (entry.relatedObject.fileType === "er-entity") {
return `${entry.relatedObject.logicalName}/${entry.relatedObject.physicalName}`;
}
return entry.relatedObject.name;
}
function getRelationId(entry) {
const relation = entry.relation;
if ("domain" in relation) {
return relation.id || relation.label || relation.kind;
}
return relation.id || relation.label || relation.kind;
}
function formatDirection(direction) {
return direction === "outgoing" ? "Outbound" : "Inbound";
}
function createDirectionBadge(direction) {
const badge = activeDocument.createElement("span");
badge.textContent = formatDirection(direction);
badge.addClass("model-weave-badge");
badge.addClass(getDirectionBadgeClass(direction));
return badge;
}
function createKindBadge(kind) {
const badge = activeDocument.createElement("span");
badge.textContent = kind || "-";
badge.addClass("model-weave-badge");
badge.addClass(getKindBadgeClass(kind));
return badge;
}
function getDirectionBadgeClass(direction) {
return direction === "outgoing" ? "model-weave-badge-outgoing" : "model-weave-badge-incoming";
}
function getKindBadgeClass(kind) {
switch (kind) {
case "inheritance":
return "model-weave-badge-inheritance";
case "implementation":
return "model-weave-badge-implementation";
case "dependency":
return "model-weave-badge-dependency";
case "composition":
return "model-weave-badge-composition";
case "aggregation":
return "model-weave-badge-aggregation";
case "association":
return "model-weave-badge-association";
case "fk":
return "model-weave-badge-fk";
default:
return "model-weave-badge-default";
}
}
function truncateValue(value, maxLength) {
if (value.length <= maxLength) {
return value;
}
return `${value.slice(0, maxLength - 3)}...`;
}
// src/renderers/source-links-renderer.ts
var import_fs = require("fs");
var import_path = __toESM(require("path"));
var import_electron = require("electron");
var import_obsidian6 = require("obsidian");
// src/i18n/en.ts
var EN_MESSAGES = {
"relationship.title": "Impact / relationships",
"relationship.copySummary": "Copy relationship summary",
"relationship.referencesFromThisObject": "References from this object",
"relationship.referencedByThisObject": "Referenced by this object",
"relationship.unresolvedReferences": "Unresolved references",
"relationship.relatedSourceLinks": "Related source links",
"relationship.valueUsage": "Value usage",
"relationship.noOutbound": "No outbound object relationships found.",
"relationship.noInbound": "No inbound object relationships found.",
"relationship.noValueUsage": "None",
"relationship.noUnresolved": "No unresolved outbound references found.",
"relationship.noRelatedSourceLinks": "No related source links found.",
"relationship.open": "Open",
"relationship.sourceLink": "Source link",
"relationship.weaveMap.title": "Weave map",
"relationship.weaveMap.description": "Visual map of related models.",
"relationship.weaveMap.viewMode": "View",
"relationship.weaveMap.compact": "Compact",
"relationship.weaveMap.full": "Full",
"relationship.overview.outgoing": "Outgoing",
"relationship.overview.incoming": "Incoming",
"relationship.overview.unresolved": "Unresolved",
"relationship.overview.sourceLinks": "Source links",
"relationship.overview.valueUsage": "Value usage",
"relationship.usedBy.screens": "Used by screens",
"relationship.usedBy.processes": "Used by processes",
"relationship.usedBy.rules": "Used by rules",
"relationship.usedBy.mappings": "Used by mappings",
"relationship.usedBy.diagrams": "Used by diagrams",
"relationship.usedBy.classes": "Used by classes",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"relationship.usedBy.dataEr": "Used by data / ER",
"relationship.usedBy.other": "Used by other models",
"relationship.references.screens": "References screens",
"relationship.references.processes": "References processes",
"relationship.references.rules": "References rules",
"relationship.references.mappings": "References mappings",
"relationship.references.diagrams": "References diagrams",
"relationship.references.classes": "References classes",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"relationship.references.dataEr": "References data / ER",
"relationship.references.other": "References other models",
"review.summary.title": "Review summary",
"review.summary.model": "Model",
"review.summary.modelType": "Model type",
"review.summary.modelId": "Model ID",
"review.summary.errors": "Errors",
"review.summary.warnings": "Warnings",
"review.summary.notes": "Notes",
"review.summary.incoming": "Incoming",
"review.summary.outgoing": "Outgoing",
"review.summary.unresolved": "Unresolved",
"review.summary.sourceLinks": "Source links",
"review.summary.weaveMap": "Weave map",
"review.summary.available": "Available",
"review.summary.notAvailable": "Not available",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"relationship.usage.one": "usage",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"relationship.usage.other": "usages",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"relationship.note.one": "note",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"relationship.note.other": "notes",
"domains.preview.title": "Domains",
"domains.preview.overview": "Overview",
"domains.preview.count": "Domains",
"domains.preview.list": "Domain list",
"domains.preview.tree": "Domain hierarchy",
"domains.preview.details": "Details",
"domains.preview.relationships": "Domain relationships",
"domains.preview.diagram": "Domain hierarchy diagram",
"domains.preview.mindmap": "Mindmap",
"domains.preview.area": "Area",
"domains.preview.treeMode": "Tree",
"domains.preview.viewMode": "Domain view mode",
"appProcess.businessFlow.direction": "Business flow direction",
"appProcess.businessFlow.direction.lr": "Left to right",
"appProcess.businessFlow.direction.td": "Top down",
"flowDiagram.viewMode": "Flow view",
"flowDiagram.viewMode.detail": "Detail",
"flowDiagram.viewMode.screen": "Screen",
/* eslint-disable obsidianmd/ui/sentence-case-locale-module -- Connect Flow is the requested toolbar label. */
"appProcess.businessFlow.connectFlow.short": "Connect Flow",
"appProcess.businessFlow.connectFlow.title": "Connect Flow",
"appProcess.businessFlow.connectFlow.selectedTitle": "Connect Flow: {stepId} selected",
"appProcess.businessFlow.connectFlow.enabled": "Connect Flow mode is on. Click a source step.",
"appProcess.businessFlow.connectFlow.disabled": "Connect Flow mode is off.",
"appProcess.businessFlow.connectFlow.selected": "Selected source step: {stepId}",
"appProcess.businessFlow.connectFlow.cleared": "Flow source selection cleared.",
"appProcess.businessFlow.connectFlow.added": "Added flow: {from} -> {to}",
"appProcess.businessFlow.connectFlow.duplicate": "Flow already exists: {from} -> {to}",
"appProcess.businessFlow.connectFlow.failed": "Could not add flow.",
"appProcess.businessFlow.connectFlow.statusReady": "Flow Connect Mode: select source step.",
"appProcess.businessFlow.connectFlow.statusSelected": "Flow Connect Mode: source '{stepId}' selected. Select target step.",
/* eslint-enable obsidianmd/ui/sentence-case-locale-module */
"domains.preview.diagramEmpty": "No domain hierarchy to display.",
"domains.preview.diagramRenderFailed": "Domain hierarchy diagram could not be rendered.",
"domains.preview.empty": "No domains defined.",
"mermaid.source.title": "Mermaid source",
"mermaid.source.copy": "Copy Mermaid",
"graph.exportPng": "Export as PNG",
"graph.exportPngOpen": "Export PNG and open",
"graph.exportPngOpenUnavailable": "Opening exported PNG is only available on desktop vaults.",
"graph.exportPngOpenFailed": "Failed to open exported PNG: {message}",
"graph.focusModeEnter": "Enter focus mode",
"graph.focusModeExit": "Exit focus mode",
"graph.viewOnlyEnter": "View only",
"graph.viewOnlyExit": "Exit view only",
"preview.openInMainPane": "Open preview in main pane",
"preview.openInMainPane.short": "Main pane",
"preview.openInNewPane": "Open preview in new pane",
"preview.openInNewPane.short": "New pane",
"viewer.lowerTab.details": "Details",
"viewer.lowerTab.relationships": "Relationships",
"viewer.lowerTab.diagnostics": "Diagnostics",
"viewer.lowerTab.sourceLinks": "Source links",
"viewer.lowerTab.mermaid": "Mermaid",
"viewer.lowerTab.empty.details": "No details available.",
"viewer.lowerTab.empty.relationships": "No relationships.",
"viewer.lowerTab.empty.diagnostics": "No diagnostics.",
"viewer.lowerTab.empty.sourceLinks": "No source links.",
"viewer.lowerTab.empty.mermaid": "Mermaid source is not available for the current renderer. Switch to a Mermaid renderer to view it.",
"diagnostics.notes": "Notes",
"diagnostics.warnings": "Warnings",
"diagnostics.errors": "Errors",
"diagnostics.openInEditor": "Open this diagnostic in the editor",
"diagnostics.summary": "Diagnostics summary",
"diagnostics.openSource": "Open location",
"diagnostics.openLocation": "Open location",
"diagnostics.openLocationTooltip": "Open the Markdown location for this diagnostic",
"diagnostics.copyMessage": "Copy message",
"diagnostics.copyMarkdown": "Copy diagnostic as Markdown",
"diagnostics.copyReference": "Copy reference",
"diagnostics.copyExpectedHeader": "Copy expected header",
"diagnostics.copyFrontmatterExample": "Copy frontmatter example",
"diagnostics.copyAll": "Copy all diagnostics",
"diagnostics.copyErrors": "Copy errors",
"diagnostics.copyWarnings": "Copy warnings",
"diagnostics.copyNotes": "Copy notes",
"diagnostics.quickFix.group": "Quick fix",
"diagnostics.quickFix.insertFrontmatter": "Insert frontmatter",
"diagnostics.quickFix.insertMissingField": "Insert missing field",
"diagnostics.quickFix.insertId": "Insert ID",
"diagnostics.quickFix.applied": "Quick fix applied.",
"diagnostics.quickFix.failed": "Quick fix could not be applied safely.",
"diagnostics.severity.error": "Error",
"diagnostics.severity.warning": "Warning",
"diagnostics.severity.note": "Note",
"diagnostics.meta.file": "File",
"diagnostics.meta.section": "Section",
"diagnostics.meta.line": "Line",
"diagnostics.meta.row": "Row",
"diagnostics.meta.reference": "Reference",
"diagnostics.meta.field": "Field",
"diagnostics.meta.severity": "Severity",
"diagnostics.meta.code": "Code",
"diagnostics.meta.message": "Message",
"diagnostics.details.title": "Diagnostic details",
"diagnostics.details.expectedHeader": "Expected header",
"diagnostics.details.actualHeader": "Actual header",
"diagnostics.details.missingColumns": "Missing columns",
"diagnostics.details.extraColumns": "Extra columns",
"diagnostics.details.unresolvedReference": "Unresolved reference",
"diagnostics.details.duplicateTarget": "Duplicate target",
"diagnostics.details.mappingRow": "Mapping row",
"diagnostics.details.frontmatterKey": "Missing frontmatter key",
"diagnostics.details.frontmatterExample": "Frontmatter example",
"diagnostics.details.requestedRenderMode": "Requested render mode",
"diagnostics.details.effectiveRenderMode": "Effective render mode",
"diagnostics.details.formatDefault": "Format default",
"diagnostics.details.diagramCompatibility": "Diagram compatibility",
"diagnostics.details.notClassDiagramCompatible": "Exists, but is excluded from class diagram rendering",
"diagnostics.details.targetType": "Target type",
"diagnostics.guidance.whatWrong": "What is wrong",
"diagnostics.guidance.likelyCause": "Likely cause",
"diagnostics.guidance.manualFix": "Manual fix",
"diagnostics.guidance.safeExample": "Safe example",
"diagnostics.guidance.specificHint": "Specific hint",
"diagnostics.guidance.frontmatter.what": "Frontmatter identifies this model and its display name.",
"diagnostics.guidance.frontmatter.cause": "This often happens after copying a file and deleting or partially editing frontmatter.",
"diagnostics.guidance.frontmatter.fix": "Restore the missing field from the format document, a template, or a valid file of the same type.",
"diagnostics.guidance.tableHeader.what": "The table header does not match the expected format.",
"diagnostics.guidance.tableHeader.cause": "Common causes are a typo in a header label, an accidental empty column at the right edge, or a table copied from another model type.",
"diagnostics.guidance.tableHeader.fix": "Compare the header with the expected header and edit only the header row first; removing an extra empty rightmost column is usually safe.",
"diagnostics.guidance.unsupportedSection.what": "This section is not supported for the current file type.",
"diagnostics.guidance.unsupportedSection.fix": "Move the information to a supported section, or define it in a separate model file with the appropriate type.",
"diagnostics.guidance.unsupportedRuleMessages.fix": "The messages section is not supported for rule files. Put rule messages in the message column of the conditions section, or define reusable text in a separate type: message file.",
"diagnostics.guidance.unsupportedAppProcessMessages.fix": "The messages section is not supported for app_process files. Use the errors section, transition notes, or define reusable text in a separate type: message file.",
"diagnostics.guidance.tableRow.what": "A table row does not match the column count or required row shape.",
"diagnostics.guidance.tableRow.cause": "Empty or malformed rows are often created by pressing tab or enter too many times in the table editor.",
"diagnostics.guidance.tableRow.fix": "Delete the empty row if it is not intentional, or compare its cells with the header before moving values.",
"diagnostics.guidance.renderMode.what": "The requested render_mode is not supported for this model, so the viewer falls back to a supported renderer.",
"diagnostics.guidance.renderMode.cause": "This usually comes from copying frontmatter from another model type or typing a renderer name by hand.",
"diagnostics.guidance.renderMode.fix": "Replace render_mode with a supported value for this format, or remove render_mode to use the configured/default renderer.",
"diagnostics.guidance.frontmatterWikilink.what": "Frontmatter values that contain [[...]] should be quoted.",
"diagnostics.guidance.frontmatterWikilink.fix": "Wrap the wikilink-like value in quotes so it stays plain text.",
"diagnostics.guidance.reference.what": "The reference could not be resolved from the indexed model files.",
"diagnostics.guidance.reference.cause": "The referenced model may not exist yet, the reference may contain a typo, or the file may be outside the indexed model set.",
"diagnostics.guidance.reference.fix": "Check spelling and use [[...]] completion when the target should exist; planned targets can stay as reminder warnings.",
"diagnostics.guidance.flowEndpoint.what": "The flow endpoint object ID is not defined in the local `Objects` table.",
"diagnostics.guidance.flowEndpoint.cause": "Common causes are a typo in `Flows.from` or `Flows.to`, a missing object row in `Objects`, or a malformed `Objects` table that prevented object IDs from being read.",
"diagnostics.guidance.flowEndpoint.fix": "Add the missing object row, or correct the from/to ID to match an existing `Objects.id`.",
"diagnostics.guidance.referenceSeparator.what": "This cell appears to contain multiple references separated by comma, japanese comma, or 'and'.",
"diagnostics.guidance.referenceSeparator.fix": "Use ' / ' between multiple model references in one cell.",
"diagnostics.guidance.referenceSeparator.example": "[[data-a]] / [[data-b]]",
"objectContext.title": "Related objects",
"objectContext.linked": "{count} linked",
"objectContext.connectionDetails": "Connection details",
"objectContext.relationDetails": "Relation details",
"objectContext.noDirectlyRelated": "No directly related objects.",
"class.preview.displayedRelations": "Displayed relations",
"class.preview.noRelationsUsed": "No relations are currently used for rendering.",
"summary.counts": "Counts",
"summary.count.triggers": "Triggers",
"summary.count.inputs": "Inputs",
"summary.count.outputs": "Outputs",
"summary.count.transitions": "Transitions",
"summary.count.steps": "Steps",
"summary.count.flows": "Flows",
"summary.count.domains": "Domains",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.count.domainSources": "Domain Sources",
"summary.count.layouts": "Layouts",
"summary.count.fields": "Fields",
"summary.count.actions": "Actions",
"summary.count.messages": "Messages",
"summary.count.localProcesses": "Local processes",
"summary.count.invokedProcesses": "Invoked processes",
"summary.count.outgoingScreens": "Outgoing screens",
"summary.detectedSections": "Detected sections",
"summary.noRows": "No rows",
"summary.section.summary": "Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.domainSourcesSummary": "Domain Sources Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.domainsSummary": "Domains Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.triggersSummary": "Triggers Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.inputsSummary": "Inputs Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.outputsSummary": "Outputs Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.stepsSummary": "Steps Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.flowsSummary": "Flows Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.transitionsSummary": "Transitions Summary",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.structureLayout": "Structure / Layout",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.uiElementsFields": "UI Elements / Fields",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.behaviorActions": "Behavior / Actions",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.localProcesses": "Local Processes",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.invokedProcesses": "Invoked Processes",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"summary.section.transitionsOutgoingScreens": "Transitions / Outgoing Screens",
"summary.section.messages": "Messages",
"summary.section.notes": "Notes",
"summary.section.layout": "Layout",
"summary.section.fields": "Fields",
"summary.section.actions": "Actions",
"summary.section.transitionsLegacy": "Transitions (legacy)",
"summary.unit.rows": "{count} rows",
"summary.unit.headings": "{count} headings",
"screen.preview.unassigned": "Unassigned",
"screen.preview.layoutMissing": "Layout is missing or undefined",
/* eslint-disable obsidianmd/ui/sentence-case-locale-module -- Source Links table labels and status text intentionally match UI/spec wording. */
"sourceLinks.title": "Source links",
"sourceLinks.help": "Open uses your OS/default app and may fail for UNC/WSL paths or unsupported file associations.",
"sourceLinks.path": "Path",
"sourceLinks.status": "Status",
"sourceLinks.resolvedPath": "Resolved Path",
"sourceLinks.notes": "Notes",
"sourceLinks.action": "Action",
"sourceLinks.copyPath": "Copy Path",
"sourceLinks.copyAllPaths": "Copy all paths",
"sourceLinks.copyAvailablePaths": "Copy available paths",
"sourceLinks.copyAsMarkdown": "Copy as Markdown",
"sourceLinks.copyMissingPaths": "Copy missing paths",
"sourceLinks.open": "Open",
"sourceLinks.openWithDefaultApp": "Open with default app",
"sourceLinks.summary.total": "Total",
"sourceLinks.summary.available": "Available",
"sourceLinks.summary.missing": "Missing",
"sourceLinks.summary.rootNotConfigured": "Source root not configured",
"sourceLinks.noLinks": "No source links are defined for this model.",
"sourceLinks.unsupportedFileUri": "unsupported file URI",
"sourceLinks.useFilesystemPath": "Use a filesystem path instead of a file URI",
"sourceLinks.unsupportedSourceRoot": "unsupported source root",
"sourceLinks.outsideSourceRoot": "outside source root",
"sourceLinks.localSourceRootNotConfigured": "Local source root is not configured",
"sourceLinks.missing": "missing",
"sourceLinks.available": "available",
"sourceLinks.availableDirectory": "available directory",
"sourceLinks.uncPathNote": "UNC/WSL path. Open may depend on your OS and app support.",
"sourceLinks.openUnavailable": "Could not open Source Link: OS open is not available.",
"sourceLinks.openFailed": "Could not open Source Link: {message}",
/* eslint-enable obsidianmd/ui/sentence-case-locale-module */
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.field.type": "type",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.field.id": "id",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.field.name": "name",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.field.kind": "kind",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.field.parent": "parent",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.field.description": "description",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.field.path": "path",
"domains.relationship.definedIn": "Defined in",
"domains.relationship.conflicts": "Conflicts",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.relationship.dfdLocalDomains": "Referenced by DFD-local Domains",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domains.relationship.dfdObjects": "Referenced by DFD objects",
"domains.relationship.parent": "Parent",
"domains.relationship.children": "Children",
"domains.relationship.none": "None",
"domains.relationship.conflictField": "{field} differs across Domains files",
"domains.value.none": "-",
"domainDiagram.preview.sources": "Domain sources",
"domainDiagram.preview.noSources": "No domain sources defined.",
"domainDiagram.preview.conflicts": "Domain source conflicts",
"domainDiagram.preview.noConflicts": "No domain source conflicts.",
"domainDiagram.preview.sourceCount": "Domain sources",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"appProcess.preview.domainSourcesPlacement": "Domain Sources / Placement",
"appProcess.preview.legacyLaneLayoutOnly": "Legacy lane placement remains layout-only and is used only when steps.domain is empty.",
"appProcess.preview.localDomains": "Local domains",
"appProcess.preview.domainPlacement": "Domain placement",
"dfd.preview.displayedObjects": "Displayed objects",
"dfd.preview.displayedFlows": "Displayed flows",
"dfd.preview.noObjects": "No objects are used for rendering.",
"dfd.preview.noFlows": "No flows are used for rendering.",
"dfd.preview.domainPlacement": "Domain placement",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"dfd.preview.resolved": "resolved",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"dfd.preview.unresolved": "unresolved",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domainDiagram.field.ref": "ref",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domainDiagram.field.status": "status",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domainDiagram.field.notes": "notes",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domainDiagram.field.conflict": "conflict",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domainDiagram.field.earlier": "earlier",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domainDiagram.field.later": "later",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"domainDiagram.field.effective": "effective",
"domainDiagram.status.ok": "OK",
"domainDiagram.status.unresolved": "Unresolved",
"domainDiagram.status.invalid-type": "Invalid type",
"domainDiagram.status.empty": "Empty",
"colorScheme.preview.title": "Color scheme",
"colorScheme.preview.applied": "Applied color scheme",
"colorScheme.preview.builtIn": "Built-in default",
"colorScheme.preview.configured": "Configured color scheme",
"colorScheme.preview.colors": "Colors",
"colorScheme.preview.swatch": "Color preview",
"colorScheme.preview.compactSwatch": "Preview",
"colorScheme.preview.compactNotes": "Notes",
"colorScheme.preview.compactSource": "Source",
"colorScheme.preview.targets": "Targets",
"colorScheme.preview.empty": "No colors defined.",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"colorScheme.preview.blankColor": "(blank)",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"colorScheme.preview.unsupportedColor": "Unsupported color value; picking a color will replace it with #RRGGBB.",
"colorScheme.preview.pick": "Pick",
"colorScheme.preview.pickAria": "Pick {column} color",
"colorScheme.preview.pickApplied": "Color updated.",
"colorScheme.preview.pickFailed": "Could not update the color cell.",
"colorScheme.preview.pickUnchanged": "Color is already set.",
"colorScheme.field.target": "Target",
"colorScheme.field.kind": "Kind",
"colorScheme.field.fill": "Fill",
"colorScheme.field.stroke": "Stroke",
"colorScheme.field.text": "Text",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"colorScheme.field.notes": "notes",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"colorScheme.field.source": "source",
"settings.section.viewer": "Viewer",
"settings.defaultClassRenderMode.name": "Default class render mode",
"settings.defaultClassRenderMode.desc": "Used for class and class_diagram files when frontmatter.render_mode is not set.",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"settings.defaultErRenderMode.name": "Default ER render mode",
"settings.defaultErRenderMode.desc": "Used for er_entity and er_diagram files when frontmatter.render_mode is not set.",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"settings.defaultDfdRenderMode.name": "Default DFD render mode",
"settings.defaultDfdRenderMode.desc": "Used for dfd_diagram files when frontmatter.render_mode is not set.",
"settings.defaultProcessRenderMode.name": "Default process render mode",
"settings.defaultProcessRenderMode.desc": "Used for app_process files when frontmatter.render_mode is not set.",
"settings.defaultBusinessFlowDirection.name": "Default business flow direction",
"settings.defaultBusinessFlowDirection.desc": "Used for app_process business flow previews when frontmatter.flow_direction is not set.",
"settings.defaultBusinessFlowDirection.lr": "Left to right",
"settings.defaultBusinessFlowDirection.td": "Top down",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module -- Flow Diagram is the requested format name.
"settings.defaultFlowDiagramViewMode.name": "Default Flow Diagram view",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module -- Flow Diagram is the requested format name.
"settings.defaultFlowDiagramViewMode.desc": "Used for a Flow Diagram initial view when frontmatter.flow_view is not set or is invalid.",
"settings.defaultScreenRenderMode.name": "Default screen render mode",
"settings.defaultScreenRenderMode.desc": "Used for screen files when frontmatter.render_mode is not set.",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"settings.defaultDomainsViewMode.name": "Default Domains view mode",
"settings.defaultDomainsViewMode.desc": "Used when frontmatter.render_mode is not set for domains files.",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"settings.defaultDomainDiagramViewMode.name": "Default Domain Diagram view mode",
"settings.defaultDomainDiagramViewMode.desc": "Used when frontmatter.render_mode is not set for domain_diagram files.",
"settings.defaultZoom.name": "Default zoom",
"settings.defaultZoom.desc": "Initial diagram zoom when no saved viewport state exists. Fit uses fit-to-view; 100% opens at actual scale.",
"settings.fontSize.name": "Font size",
"settings.fontSize.desc": "Adjusts the base preview text size across viewers.",
"settings.nodeDensity.name": "Node density",
"settings.nodeDensity.desc": "Controls diagram compactness where supported. Compact reduces padding and gaps; relaxed gives more breathing room.",
"settings.relationshipView.name": "Relationship view",
"settings.relationshipView.desc": "Show object-level inbound/outbound relationships in previews. Disable this for large vaults or reverse engineering workflows when preview speed matters more.",
"settings.showMermaidRenderDebug.name": "Show Mermaid render debug",
"settings.showMermaidRenderDebug.desc": "Show collapsed Mermaid rendering diagnostics under Mermaid diagrams. Mermaid source remains available regardless of this setting.",
"settings.uiLanguage.name": "UI language",
// eslint-disable-next-line obsidianmd/ui/sentence-case-locale-module
"settings.uiLanguage.desc": "Language for Model Weave viewer and settings captions. Auto follows Obsidian when available.",
"settings.localSourceRoot.name": "Local source root",
"settings.localSourceRoot.desc": "Base directory used to resolve relative source links outside the Obsidian vault.",
"settings.defaultColorScheme.name": "Default color scheme",
"settings.defaultColorScheme.desc": "Vault ref or path to a color_scheme file used by supported diagrams.",
"settings.refreshOpenViews.name": "Refresh open views",
"settings.refreshOpenViews.desc": "Re-render open previews using the current settings.",
"settings.refreshOpenViews.button": "Refresh",
"settings.refreshOpenViews.notice": "Refreshed open views",
"settings.option.auto": "Auto",
"settings.option.english": "English",
"settings.option.japanese": "Japanese",
"settings.option.custom": "Custom",
"settings.option.mermaid": "Mermaid",
"settings.option.mermaidDetail": "Mermaid detail",
"settings.option.fit": "Fit",
"settings.option.small": "Small",
"settings.option.normal": "Normal",
"settings.option.large": "Large",
"settings.option.compact": "Compact",
"settings.option.relaxed": "Relaxed"
};
// src/i18n/ja.ts
var JA_MESSAGES = {
"relationship.title": "\u5F71\u97FF / \u95A2\u9023",
"relationship.copySummary": "\u95A2\u9023\u30B5\u30DE\u30EA\u3092\u30B3\u30D4\u30FC",
"relationship.referencesFromThisObject": "\u3053\u306E\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u304C\u53C2\u7167\u3057\u3066\u3044\u308B\u3082\u306E",
"relationship.referencedByThisObject": "\u3053\u306E\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u53C2\u7167\u3057\u3066\u3044\u308B\u3082\u306E",
"relationship.unresolvedReferences": "\u672A\u89E3\u6C7A\u306E\u53C2\u7167",
"relationship.relatedSourceLinks": "\u95A2\u9023\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF",
"relationship.valueUsage": "\u5024\u306E\u5229\u7528\u72B6\u6CC1",
"relationship.noOutbound": "\u53C2\u7167\u5148\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"relationship.noInbound": "\u3053\u306E\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u53C2\u7167\u3057\u3066\u3044\u308B\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"relationship.noValueUsage": "\u306A\u3057",
"relationship.noUnresolved": "\u672A\u89E3\u6C7A\u306E\u53C2\u7167\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"relationship.noRelatedSourceLinks": "\u95A2\u9023\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"relationship.open": "\u958B\u304F",
"relationship.sourceLink": "\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF",
"relationship.weaveMap.title": "Weave Map",
"relationship.weaveMap.description": "\u95A2\u9023\u30E2\u30C7\u30EB\u3092\u8996\u899A\u7684\u306B\u8868\u793A\u3059\u308B\u30DE\u30C3\u30D7\u3067\u3059\u3002",
"relationship.weaveMap.viewMode": "\u8868\u793A",
"relationship.weaveMap.compact": "\u96C6\u7D04",
"relationship.weaveMap.full": "\u8A73\u7D30",
"relationship.overview.outgoing": "\u53C2\u7167\u5148",
"relationship.overview.incoming": "\u88AB\u53C2\u7167",
"relationship.overview.unresolved": "\u672A\u89E3\u6C7A",
"relationship.overview.sourceLinks": "\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF",
"relationship.overview.valueUsage": "\u5024\u306E\u5229\u7528",
"relationship.usedBy.screens": "Screens\u3067\u4F7F\u7528",
"relationship.usedBy.processes": "Processes\u3067\u4F7F\u7528",
"relationship.usedBy.rules": "Rules\u3067\u4F7F\u7528",
"relationship.usedBy.mappings": "Mappings\u3067\u4F7F\u7528",
"relationship.usedBy.diagrams": "Diagrams\u3067\u4F7F\u7528",
"relationship.usedBy.classes": "Classes\u3067\u4F7F\u7528",
"relationship.usedBy.dataEr": "Data / ER\u3067\u4F7F\u7528",
"relationship.usedBy.other": "\u305D\u306E\u4ED6\u30E2\u30C7\u30EB\u3067\u4F7F\u7528",
"relationship.references.screens": "Screens\u3092\u53C2\u7167",
"relationship.references.processes": "Processes\u3092\u53C2\u7167",
"relationship.references.rules": "Rules\u3092\u53C2\u7167",
"relationship.references.mappings": "Mappings\u3092\u53C2\u7167",
"relationship.references.diagrams": "Diagrams\u3092\u53C2\u7167",
"relationship.references.classes": "Classes\u3092\u53C2\u7167",
"relationship.references.dataEr": "Data / ER\u3092\u53C2\u7167",
"relationship.references.other": "\u305D\u306E\u4ED6\u30E2\u30C7\u30EB\u3092\u53C2\u7167",
"review.summary.title": "\u30EC\u30D3\u30E5\u30FC\u6982\u8981",
"review.summary.model": "\u30E2\u30C7\u30EB",
"review.summary.modelType": "\u30E2\u30C7\u30EB\u7A2E\u5225",
"review.summary.modelId": "\u30E2\u30C7\u30EBID",
"review.summary.errors": "\u30A8\u30E9\u30FC",
"review.summary.warnings": "\u8B66\u544A",
"review.summary.notes": "\u30CE\u30FC\u30C8",
"review.summary.incoming": "\u88AB\u53C2\u7167",
"review.summary.outgoing": "\u53C2\u7167\u5148",
"review.summary.unresolved": "\u672A\u89E3\u6C7A",
"review.summary.sourceLinks": "\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF",
"review.summary.weaveMap": "Weave Map",
"review.summary.available": "\u5229\u7528\u53EF\u80FD",
"review.summary.notAvailable": "\u5229\u7528\u4E0D\u53EF",
"relationship.usage.one": "\u4EF6",
"relationship.usage.other": "\u4EF6",
"relationship.note.one": "\u4EF6\u306E\u30E1\u30E2",
"relationship.note.other": "\u4EF6\u306E\u30E1\u30E2",
"domains.preview.title": "Domains",
"domains.preview.overview": "\u6982\u8981",
"domains.preview.count": "Domain \u6570",
"domains.preview.list": "Domain \u4E00\u89A7",
"domains.preview.tree": "Domain \u968E\u5C64",
"domains.preview.details": "\u8A73\u7D30\u60C5\u5831",
"domains.preview.relationships": "Domain \u95A2\u4FC2",
"domains.preview.diagram": "Domain \u968E\u5C64\u56F3",
"domains.preview.mindmap": "\u30DE\u30A4\u30F3\u30C9\u30DE\u30C3\u30D7",
"domains.preview.area": "\u9818\u57DF",
"domains.preview.treeMode": "\u30C4\u30EA\u30FC",
"domains.preview.viewMode": "Domain \u8868\u793A\u30E2\u30FC\u30C9",
"appProcess.businessFlow.direction": "Business Flow \u65B9\u5411",
"appProcess.businessFlow.direction.lr": "\u5DE6\u304B\u3089\u53F3",
"appProcess.businessFlow.direction.td": "\u4E0A\u304B\u3089\u4E0B",
"flowDiagram.viewMode": "Flow view",
"flowDiagram.viewMode.detail": "Detail",
"flowDiagram.viewMode.screen": "Screen",
"appProcess.businessFlow.connectFlow.short": "Flow\u63A5\u7D9A",
"appProcess.businessFlow.connectFlow.title": "Flow\u63A5\u7D9A",
"appProcess.businessFlow.connectFlow.selectedTitle": "Flow\u63A5\u7D9A: {stepId} \u3092\u9078\u629E\u4E2D",
"appProcess.businessFlow.connectFlow.enabled": "Flow\u63A5\u7D9A\u30E2\u30FC\u30C9\u3092\u30AA\u30F3\u306B\u3057\u307E\u3057\u305F\u3002\u63A5\u7D9A\u5143\u30B9\u30C6\u30C3\u30D7\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"appProcess.businessFlow.connectFlow.disabled": "Flow\u63A5\u7D9A\u30E2\u30FC\u30C9\u3092\u30AA\u30D5\u306B\u3057\u307E\u3057\u305F\u3002",
"appProcess.businessFlow.connectFlow.selected": "\u63A5\u7D9A\u5143\u30B9\u30C6\u30C3\u30D7\u3092\u9078\u629E\u3057\u307E\u3057\u305F: {stepId}",
"appProcess.businessFlow.connectFlow.cleared": "\u63A5\u7D9A\u5143\u30B9\u30C6\u30C3\u30D7\u306E\u9078\u629E\u3092\u89E3\u9664\u3057\u307E\u3057\u305F\u3002",
"appProcess.businessFlow.connectFlow.added": "Flow\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F: {from} -> {to}",
"appProcess.businessFlow.connectFlow.duplicate": "Flow\u306F\u3059\u3067\u306B\u5B58\u5728\u3057\u307E\u3059: {from} -> {to}",
"appProcess.businessFlow.connectFlow.failed": "Flow\u3092\u8FFD\u52A0\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
"appProcess.businessFlow.connectFlow.statusReady": "Flow\u63A5\u7D9A\u30E2\u30FC\u30C9: \u63A5\u7D9A\u5143Step\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"appProcess.businessFlow.connectFlow.statusSelected": "Flow\u63A5\u7D9A\u30E2\u30FC\u30C9: \u63A5\u7D9A\u5143 '{stepId}' \u3092\u9078\u629E\u4E2D\u3002\u63A5\u7D9A\u5148Step\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"domains.preview.diagramEmpty": "\u8868\u793A\u3067\u304D\u308B Domain \u968E\u5C64\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
"domains.preview.diagramRenderFailed": "Domain \u968E\u5C64\u56F3\u3092\u63CF\u753B\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
"domains.preview.empty": "Domain \u306F\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
"mermaid.source.title": "Mermaid \u30BD\u30FC\u30B9",
"mermaid.source.copy": "Mermaid \u3092\u30B3\u30D4\u30FC",
"graph.exportPng": "PNG\u3068\u3057\u3066\u66F8\u304D\u51FA\u3057",
"graph.exportPngOpen": "PNG\u3092\u66F8\u304D\u51FA\u3057\u3066\u958B\u304F",
"graph.exportPngOpenUnavailable": "\u66F8\u304D\u51FA\u3057\u305F PNG \u3092\u958B\u304F\u6A5F\u80FD\u306F\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7 Vault \u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002",
"graph.exportPngOpenFailed": "\u66F8\u304D\u51FA\u3057\u305F PNG \u3092\u958B\u3051\u307E\u305B\u3093\u3067\u3057\u305F: {message}",
"graph.focusModeEnter": "\u30D5\u30A9\u30FC\u30AB\u30B9\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048",
"graph.focusModeExit": "\u30D5\u30A9\u30FC\u30AB\u30B9\u30E2\u30FC\u30C9\u3092\u7D42\u4E86",
"graph.viewOnlyEnter": "View Only \u306B\u5207\u308A\u66FF\u3048",
"graph.viewOnlyExit": "View Only \u3092\u7D42\u4E86",
"preview.openInMainPane": "\u30E1\u30A4\u30F3\u30DA\u30A4\u30F3\u3067\u958B\u304F",
"preview.openInMainPane.short": "\u30E1\u30A4\u30F3\u30DA\u30A4\u30F3",
"preview.openInNewPane": "\u65B0\u3057\u3044\u30DA\u30A4\u30F3\u3067\u958B\u304F",
"preview.openInNewPane.short": "\u65B0\u3057\u3044\u30DA\u30A4\u30F3",
"viewer.lowerTab.details": "\u8A73\u7D30",
"viewer.lowerTab.relationships": "\u95A2\u9023",
"viewer.lowerTab.diagnostics": "\u8A3A\u65AD",
"viewer.lowerTab.sourceLinks": "\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF",
"viewer.lowerTab.mermaid": "Mermaid",
"viewer.lowerTab.empty.details": "\u8868\u793A\u3067\u304D\u308B\u8A73\u7D30\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"viewer.lowerTab.empty.relationships": "\u95A2\u9023\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"viewer.lowerTab.empty.diagnostics": "\u8A3A\u65AD\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"viewer.lowerTab.empty.sourceLinks": "\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"viewer.lowerTab.empty.mermaid": "\u73FE\u5728\u306E renderer \u3067\u306F Mermaid \u30BD\u30FC\u30B9\u3092\u8868\u793A\u3067\u304D\u307E\u305B\u3093\u3002Mermaid renderer \u306B\u5207\u308A\u66FF\u3048\u308B\u3068\u8868\u793A\u3067\u304D\u307E\u3059\u3002",
"diagnostics.notes": "\u30CE\u30FC\u30C8",
"diagnostics.warnings": "\u8B66\u544A",
"diagnostics.errors": "\u30A8\u30E9\u30FC",
"diagnostics.openInEditor": "\u3053\u306E\u8A3A\u65AD\u3092\u30A8\u30C7\u30A3\u30BF\u30FC\u3067\u958B\u304F",
"diagnostics.summary": "\u8A3A\u65AD\u30B5\u30DE\u30EA",
"diagnostics.openSource": "\u8A72\u5F53\u7B87\u6240\u3092\u958B\u304F",
"diagnostics.openLocation": "\u8A72\u5F53\u7B87\u6240\u3092\u958B\u304F",
"diagnostics.openLocationTooltip": "\u3053\u306E\u8A3A\u65AD\u304C\u767A\u751F\u3057\u305FMarkdown\u4E0A\u306E\u4F4D\u7F6E\u3092\u958B\u304D\u307E\u3059",
"diagnostics.copyMessage": "\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u30B3\u30D4\u30FC",
"diagnostics.copyMarkdown": "\u8A3A\u65AD\u3092Markdown\u3067\u30B3\u30D4\u30FC",
"diagnostics.copyReference": "\u53C2\u7167\u5024\u3092\u30B3\u30D4\u30FC",
"diagnostics.copyExpectedHeader": "\u671F\u5F85\u30D8\u30C3\u30C0\u30FC\u3092\u30B3\u30D4\u30FC",
"diagnostics.copyFrontmatterExample": "frontmatter \u4F8B\u3092\u30B3\u30D4\u30FC",
"diagnostics.copyAll": "\u3059\u3079\u3066\u30B3\u30D4\u30FC",
"diagnostics.copyErrors": "\u30A8\u30E9\u30FC\u3092\u30B3\u30D4\u30FC",
"diagnostics.copyWarnings": "\u8B66\u544A\u3092\u30B3\u30D4\u30FC",
"diagnostics.copyNotes": "\u30CE\u30FC\u30C8\u3092\u30B3\u30D4\u30FC",
"diagnostics.quickFix.group": "\u30AF\u30A4\u30C3\u30AF\u4FEE\u6B63",
"diagnostics.quickFix.insertFrontmatter": "frontmatter\u3092\u633F\u5165",
"diagnostics.quickFix.insertMissingField": "\u4E0D\u8DB3\u9805\u76EE\u3092\u633F\u5165",
"diagnostics.quickFix.insertId": "id\u3092\u633F\u5165",
"diagnostics.quickFix.applied": "\u30AF\u30A4\u30C3\u30AF\u4FEE\u6B63\u3092\u9069\u7528\u3057\u307E\u3057\u305F\u3002",
"diagnostics.quickFix.failed": "\u5B89\u5168\u306B\u30AF\u30A4\u30C3\u30AF\u4FEE\u6B63\u3092\u9069\u7528\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
"diagnostics.severity.error": "\u30A8\u30E9\u30FC",
"diagnostics.severity.warning": "\u8B66\u544A",
"diagnostics.severity.note": "\u30CE\u30FC\u30C8",
"diagnostics.meta.file": "\u30D5\u30A1\u30A4\u30EB",
"diagnostics.meta.section": "\u30BB\u30AF\u30B7\u30E7\u30F3",
"diagnostics.meta.line": "\u884C",
"diagnostics.meta.row": "\u884C\u756A\u53F7",
"diagnostics.meta.reference": "\u53C2\u7167\u5024",
"diagnostics.meta.field": "\u30D5\u30A3\u30FC\u30EB\u30C9",
"diagnostics.meta.severity": "\u91CD\u8981\u5EA6",
"diagnostics.meta.code": "\u30B3\u30FC\u30C9",
"diagnostics.meta.message": "\u30E1\u30C3\u30BB\u30FC\u30B8",
"diagnostics.details.title": "\u8A3A\u65AD\u8A73\u7D30",
"diagnostics.details.expectedHeader": "\u671F\u5F85\u3055\u308C\u308B\u30D8\u30C3\u30C0\u30FC",
"diagnostics.details.actualHeader": "\u5B9F\u969B\u306E\u30D8\u30C3\u30C0\u30FC",
"diagnostics.details.missingColumns": "\u4E0D\u8DB3\u3057\u3066\u3044\u308B\u5217",
"diagnostics.details.extraColumns": "\u4F59\u5206\u306A\u5217",
"diagnostics.details.unresolvedReference": "\u672A\u89E3\u6C7A\u306E\u53C2\u7167",
"diagnostics.details.duplicateTarget": "\u91CD\u8907\u3057\u3066\u3044\u308B\u5BFE\u8C61",
"diagnostics.details.mappingRow": "Mapping row",
"diagnostics.details.frontmatterKey": "\u4E0D\u8DB3\u3057\u3066\u3044\u308B frontmatter key",
"diagnostics.details.frontmatterExample": "frontmatter \u4F8B",
"diagnostics.details.requestedRenderMode": "\u8981\u6C42\u3055\u308C\u305F\u63CF\u753B\u30E2\u30FC\u30C9",
"diagnostics.details.effectiveRenderMode": "\u5B9F\u969B\u306E\u63CF\u753B\u30E2\u30FC\u30C9",
"diagnostics.details.formatDefault": "format \u65E2\u5B9A",
"diagnostics.details.diagramCompatibility": "\u56F3\u3068\u306E\u4E92\u63DB\u6027",
"diagnostics.details.notClassDiagramCompatible": "\u5B58\u5728\u3057\u307E\u3059\u304C\u3001Class Diagram \u306E\u63CF\u753B\u5BFE\u8C61\u5916\u3067\u3059",
"diagnostics.details.targetType": "\u53C2\u7167\u5148 type",
"diagnostics.guidance.whatWrong": "\u4F55\u304C\u8D77\u304D\u3066\u3044\u308B\u304B",
"diagnostics.guidance.likelyCause": "\u3088\u304F\u3042\u308B\u539F\u56E0",
"diagnostics.guidance.manualFix": "\u624B\u52D5\u3067\u306E\u76F4\u3057\u65B9",
"diagnostics.guidance.safeExample": "\u5B89\u5168\u306A\u4F8B",
"diagnostics.guidance.specificHint": "\u88DC\u8DB3\u30D2\u30F3\u30C8",
"diagnostics.guidance.frontmatter.what": "Model Weave \u306F frontmatter \u3067\u30E2\u30C7\u30EB\u306E type\u3001id\u3001\u8868\u793A\u540D\u3092\u8B58\u5225\u3057\u307E\u3059\u3002",
"diagnostics.guidance.frontmatter.cause": "\u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u3092\u30B3\u30D4\u30FC\u3057\u305F\u3042\u3068\u3001YAML frontmatter \u3092\u524A\u9664\u3057\u305F\u308A\u4E00\u90E8\u3060\u3051\u7DE8\u96C6\u3057\u305F\u3068\u304D\u306B\u3088\u304F\u8D77\u304D\u307E\u3059\u3002",
"diagnostics.guidance.frontmatter.fix": "\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u6587\u66F8\u3001\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3001\u307E\u305F\u306F\u540C\u3058 type \u306E\u6B63\u3057\u3044\u30D5\u30A1\u30A4\u30EB\u3092\u53C2\u8003\u306B\u3001\u4E0D\u8DB3\u3057\u3066\u3044\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002id/name \u306F\u30D5\u30A1\u30A4\u30EB\u3068\u77DB\u76FE\u3057\u306A\u3044\u5024\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.tableHeader.what": "\u30C6\u30FC\u30D6\u30EB\u30D8\u30C3\u30C0\u30FC\u304C Model Weave \u306E\u671F\u5F85\u3059\u308B\u5F62\u5F0F\u3068\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
"diagnostics.guidance.tableHeader.cause": "\u30D8\u30C3\u30C0\u30FC\u540D\u306E typo\u3001\u53F3\u7AEF\u306B\u7A7A\u306E\u5217\u3092\u8FFD\u52A0\u3057\u3066\u3057\u307E\u3063\u305F\u3001\u5225\u306E\u30E2\u30C7\u30EB type \u306E\u8868\u3092\u30B3\u30D4\u30FC\u3057\u305F\u3001\u306A\u3069\u304C\u3088\u304F\u3042\u308B\u539F\u56E0\u3067\u3059\u3002",
"diagnostics.guidance.tableHeader.fix": "\u307E\u305A\u671F\u5F85\u30D8\u30C3\u30C0\u30FC\u3068\u73FE\u5728\u306E\u30D8\u30C3\u30C0\u30FC\u3092\u898B\u6BD4\u3079\u3001\u30D8\u30C3\u30C0\u30FC\u884C\u3060\u3051\u3092\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u53F3\u7AEF\u306B\u4F59\u5206\u306A\u7A7A\u5217\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u305D\u306E\u7A7A\u5217\u3092\u524A\u9664\u3059\u308B\u306E\u304C\u901A\u5E38\u5B89\u5168\u3067\u3059\u3002",
"diagnostics.guidance.unsupportedSection.what": "\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u306F\u73FE\u5728\u306E\u30D5\u30A1\u30A4\u30EB type \u3067\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
"diagnostics.guidance.unsupportedSection.fix": "\u3053\u306E\u30D5\u30A1\u30A4\u30EB type \u3067\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u308B\u30BB\u30AF\u30B7\u30E7\u30F3\u3078\u79FB\u3059\u304B\u3001\u9069\u5207\u306A type \u306E\u5225 Model Weave \u30D5\u30A1\u30A4\u30EB\u3068\u3057\u3066\u5B9A\u7FA9\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.unsupportedRuleMessages.fix": "rule \u30D5\u30A1\u30A4\u30EB\u3067\u306F ## Messages \u30BB\u30AF\u30B7\u30E7\u30F3\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002rule \u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u306F ## Conditions \u306E message \u5217\u306B\u8A18\u8FF0\u3059\u308B\u304B\u3001\u518D\u5229\u7528\u3059\u308B\u6587\u8A00\u306F type: message \u30D5\u30A1\u30A4\u30EB\u3068\u3057\u3066\u5B9A\u7FA9\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.unsupportedAppProcessMessages.fix": "app_process \u30D5\u30A1\u30A4\u30EB\u3067\u306F ## Messages \u30BB\u30AF\u30B7\u30E7\u30F3\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002## Errors\u3001Transitions.notes\u3001\u307E\u305F\u306F\u518D\u5229\u7528\u3059\u308B\u6587\u8A00\u3092 type: message \u30D5\u30A1\u30A4\u30EB\u3068\u3057\u3066\u5B9A\u7FA9\u3059\u308B\u3053\u3068\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.tableRow.what": "\u30C6\u30FC\u30D6\u30EB\u884C\u306E\u5217\u6570\u307E\u305F\u306F\u5FC5\u9808\u5024\u304C\u3001\u30D8\u30C3\u30C0\u30FC\u3084\u671F\u5F85\u5F62\u5F0F\u3068\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
"diagnostics.guidance.tableRow.cause": "\u30C6\u30FC\u30D6\u30EB editor \u3067 Tab \u3084 Enter \u3092\u62BC\u3057\u3059\u304E\u3066\u3001\u7A7A\u884C\u3084\u5D29\u308C\u305F\u884C\u304C\u3067\u304D\u305F\u5834\u5408\u306B\u3088\u304F\u8D77\u304D\u307E\u3059\u3002",
"diagnostics.guidance.tableRow.fix": "\u610F\u56F3\u3057\u306A\u3044\u7A7A\u884C\u306A\u3089\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u5024\u304C\u5165\u3063\u3066\u3044\u308B\u884C\u306E\u5834\u5408\u306F\u3001\u79FB\u52D5\u3059\u308B\u524D\u306B\u30D8\u30C3\u30C0\u30FC\u3068\u30BB\u30EB\u6570\u3092\u898B\u6BD4\u3079\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.renderMode.what": "\u6307\u5B9A\u3055\u308C\u305F render_mode \u306F\u3053\u306E\u30E2\u30C7\u30EB\u3067\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u305F\u3081\u3001viewer \u306F\u5229\u7528\u53EF\u80FD\u306A renderer \u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059\u3002",
"diagnostics.guidance.renderMode.cause": "\u5225\u306E\u30E2\u30C7\u30EB type \u304B\u3089 frontmatter \u3092\u30B3\u30D4\u30FC\u3057\u305F\u3001\u307E\u305F\u306F renderer \u540D\u3092\u624B\u5165\u529B\u3057\u305F\u3068\u304D\u306B\u3088\u304F\u8D77\u304D\u307E\u3059\u3002",
"diagnostics.guidance.renderMode.fix": "\u3053\u306E format \u3067\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u308B\u5024\u306B\u7F6E\u304D\u63DB\u3048\u308B\u304B\u3001render_mode \u3092\u524A\u9664\u3057\u3066\u8A2D\u5B9A/\u65E2\u5B9A\u306E renderer \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.frontmatterWikilink.what": "[[...]] \u3092\u542B\u3080 YAML frontmatter \u306E\u5024\u306F\u5F15\u7528\u7B26\u3067\u56F2\u3080\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
"diagnostics.guidance.frontmatterWikilink.fix": "Wikilink \u98A8\u306E\u5024\u3092\u5F15\u7528\u7B26\u3067\u56F2\u307F\u3001YAML \u304C\u6587\u5B57\u5217\u3068\u3057\u3066\u6271\u3048\u308B\u3088\u3046\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.reference.what": "\u53C2\u7167\u5148\u304C indexed Model Weave files \u304B\u3089\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
"diagnostics.guidance.reference.cause": "\u53C2\u7167\u5148\u30E2\u30C7\u30EB\u304C\u307E\u3060\u4F5C\u6210\u3055\u308C\u3066\u3044\u306A\u3044\u3001id/link \u306B typo \u304C\u3042\u308B\u3001\u307E\u305F\u306F indexed model set \u306E\u5916\u306B\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
"diagnostics.guidance.reference.fix": "\u65E2\u306B\u5B58\u5728\u3059\u308B\u306F\u305A\u306E\u53C2\u7167\u306A\u3089 spelling \u3092\u78BA\u8A8D\u3057\u3001\u7DE8\u96C6\u6642\u306F Obsidian \u306E [[...]] \u88DC\u5B8C\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002\u5F8C\u3067\u4F5C\u308B\u4E88\u5B9A\u306E\u30E2\u30C7\u30EB\u306A\u3089\u3001\u3053\u306E warning \u3092\u30EA\u30DE\u30A4\u30F3\u30C0\u30FC\u3068\u3057\u3066\u6B8B\u305B\u307E\u3059\u3002",
"diagnostics.guidance.flowEndpoint.what": "flow \u306E endpoint object ID \u304C\u30ED\u30FC\u30AB\u30EB\u306E Objects \u30C6\u30FC\u30D6\u30EB\u306B\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
"diagnostics.guidance.flowEndpoint.cause": "Flows.from / Flows.to \u306E typo\u3001Objects \u306E object \u884C\u306E\u4E0D\u8DB3\u3001\u307E\u305F\u306F Objects \u30C6\u30FC\u30D6\u30EB\u306E\u30D8\u30C3\u30C0\u30FC\u5D29\u308C\u3067 object ID \u3092\u8AAD\u307F\u53D6\u308C\u306A\u304B\u3063\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
"diagnostics.guidance.flowEndpoint.fix": "\u4E0D\u8DB3\u3057\u3066\u3044\u308B object \u884C\u3092\u8FFD\u52A0\u3059\u308B\u304B\u3001from/to ID \u3092\u65E2\u5B58\u306E Objects.id \u3068\u4E00\u81F4\u3059\u308B\u3088\u3046\u306B\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"diagnostics.guidance.referenceSeparator.what": "\u3053\u306E\u30BB\u30EB\u306B\u306F\u3001\u30AB\u30F3\u30DE\u3001\u65E5\u672C\u8A9E\u306E\u8AAD\u70B9\u3001and \u306A\u3069\u3067\u533A\u5207\u3089\u308C\u305F\u8907\u6570\u53C2\u7167\u304C\u542B\u307E\u308C\u3066\u3044\u308B\u3088\u3046\u3067\u3059\u3002",
"diagnostics.guidance.referenceSeparator.fix": "Model Weave \u3067\u306F\u30011\u3064\u306E\u30BB\u30EB\u5185\u306E\u8907\u6570\u30E2\u30C7\u30EB\u53C2\u7167\u306F ' / ' \u3067\u533A\u5207\u308B\u3053\u3068\u3092\u63A8\u5968\u3057\u307E\u3059\u3002",
"diagnostics.guidance.referenceSeparator.example": "[[DATA-A]] / [[DATA-B]]",
"objectContext.title": "\u95A2\u9023\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8",
"objectContext.linked": "{count} \u4EF6\u306E\u95A2\u9023",
"objectContext.connectionDetails": "\u63A5\u7D9A\u8A73\u7D30",
"objectContext.relationDetails": "Relation \u8A73\u7D30",
"objectContext.noDirectlyRelated": "\u76F4\u63A5\u95A2\u4FC2\u3059\u308B\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"class.preview.displayedRelations": "\u8868\u793A\u4E2D\u306E\u95A2\u4FC2",
"class.preview.noRelationsUsed": "\u63CF\u753B\u306B\u4F7F\u308F\u308C\u3066\u3044\u308B\u95A2\u4FC2\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"summary.counts": "\u4EF6\u6570",
"summary.count.triggers": "\u30C8\u30EA\u30AC\u30FC",
"summary.count.inputs": "\u5165\u529B",
"summary.count.outputs": "\u51FA\u529B",
"summary.count.transitions": "\u9077\u79FB",
"summary.count.steps": "\u30B9\u30C6\u30C3\u30D7",
"summary.count.flows": "\u30D5\u30ED\u30FC",
"summary.count.domains": "Domains",
"summary.count.domainSources": "Domain Sources",
"summary.count.layouts": "\u30EC\u30A4\u30A2\u30A6\u30C8",
"summary.count.fields": "\u30D5\u30A3\u30FC\u30EB\u30C9",
"summary.count.actions": "\u30A2\u30AF\u30B7\u30E7\u30F3",
"summary.count.messages": "\u30E1\u30C3\u30BB\u30FC\u30B8",
"summary.count.localProcesses": "\u30ED\u30FC\u30AB\u30EB\u30D7\u30ED\u30BB\u30B9",
"summary.count.invokedProcesses": "\u547C\u3073\u51FA\u3057\u5148\u30D7\u30ED\u30BB\u30B9",
"summary.count.outgoingScreens": "\u9077\u79FB\u5148\u753B\u9762",
"summary.detectedSections": "\u691C\u51FA\u3055\u308C\u305F\u30BB\u30AF\u30B7\u30E7\u30F3",
"summary.noRows": "\u884C\u306F\u3042\u308A\u307E\u305B\u3093",
"summary.section.summary": "\u6982\u8981",
"summary.section.domainSourcesSummary": "\u30C9\u30E1\u30A4\u30F3\u30BD\u30FC\u30B9\u6982\u8981",
"summary.section.domainsSummary": "\u30C9\u30E1\u30A4\u30F3\u6982\u8981",
"summary.section.triggersSummary": "\u30C8\u30EA\u30AC\u30FC\u6982\u8981",
"summary.section.inputsSummary": "\u5165\u529B\u6982\u8981",
"summary.section.outputsSummary": "\u51FA\u529B\u6982\u8981",
"summary.section.stepsSummary": "\u30B9\u30C6\u30C3\u30D7\u6982\u8981",
"summary.section.flowsSummary": "\u30D5\u30ED\u30FC\u6982\u8981",
"summary.section.transitionsSummary": "\u9077\u79FB\u6982\u8981",
"summary.section.structureLayout": "\u69CB\u9020 / \u30EC\u30A4\u30A2\u30A6\u30C8",
"summary.section.uiElementsFields": "UI\u8981\u7D20 / \u30D5\u30A3\u30FC\u30EB\u30C9",
"summary.section.behaviorActions": "\u632F\u308B\u821E\u3044 / \u30A2\u30AF\u30B7\u30E7\u30F3",
"summary.section.localProcesses": "\u30ED\u30FC\u30AB\u30EB\u30D7\u30ED\u30BB\u30B9",
"summary.section.invokedProcesses": "\u547C\u3073\u51FA\u3057\u5148\u30D7\u30ED\u30BB\u30B9",
"summary.section.transitionsOutgoingScreens": "\u9077\u79FB / \u9077\u79FB\u5148\u753B\u9762",
"summary.section.messages": "\u30E1\u30C3\u30BB\u30FC\u30B8",
"summary.section.notes": "\u30CE\u30FC\u30C8",
"summary.section.layout": "\u30EC\u30A4\u30A2\u30A6\u30C8",
"summary.section.fields": "\u30D5\u30A3\u30FC\u30EB\u30C9",
"summary.section.actions": "\u30A2\u30AF\u30B7\u30E7\u30F3",
"summary.section.transitionsLegacy": "\u9077\u79FB\uFF08\u65E7\u5F62\u5F0F\uFF09",
"summary.unit.rows": "{count}\u884C",
"summary.unit.headings": "{count}\u898B\u51FA\u3057",
"screen.preview.unassigned": "\u672A\u5206\u985E",
"screen.preview.layoutMissing": "layout \u304C\u672A\u6307\u5B9A\u307E\u305F\u306F\u672A\u5B9A\u7FA9\u3067\u3059",
"sourceLinks.title": "\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF",
"sourceLinks.help": "\u958B\u304F\u64CD\u4F5C\u306F OS \u306E\u65E2\u5B9A\u30A2\u30D7\u30EA\u3092\u4F7F\u7528\u3057\u307E\u3059\u3002UNC/WSL \u30D1\u30B9\u3084\u672A\u5BFE\u5FDC\u306E\u95A2\u9023\u4ED8\u3051\u3067\u306F\u5931\u6557\u3059\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u3002",
"sourceLinks.path": "\u30D1\u30B9",
"sourceLinks.status": "\u72B6\u614B",
"sourceLinks.resolvedPath": "\u89E3\u6C7A\u6E08\u307F\u30D1\u30B9",
"sourceLinks.notes": "\u5099\u8003",
"sourceLinks.action": "\u64CD\u4F5C",
"sourceLinks.copyPath": "\u30D1\u30B9\u3092\u30B3\u30D4\u30FC",
"sourceLinks.copyAllPaths": "\u3059\u3079\u3066\u30B3\u30D4\u30FC",
"sourceLinks.copyAvailablePaths": "\u5229\u7528\u53EF\u80FD\u306A\u30D1\u30B9\u3092\u30B3\u30D4\u30FC",
"sourceLinks.copyAsMarkdown": "Markdown\u3067\u30B3\u30D4\u30FC",
"sourceLinks.copyMissingPaths": "\u898B\u3064\u304B\u3089\u306A\u3044\u30D1\u30B9\u3092\u30B3\u30D4\u30FC",
"sourceLinks.open": "\u958B\u304F",
"sourceLinks.openWithDefaultApp": "\u65E2\u5B9A\u30A2\u30D7\u30EA\u3067\u958B\u304F",
"sourceLinks.summary.total": "\u5408\u8A08",
"sourceLinks.summary.available": "\u5229\u7528\u53EF\u80FD",
"sourceLinks.summary.missing": "\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
"sourceLinks.summary.rootNotConfigured": "Source root \u672A\u8A2D\u5B9A",
"sourceLinks.noLinks": "\u3053\u306E\u30E2\u30C7\u30EB\u306B\u306F\u30BD\u30FC\u30B9\u30EA\u30F3\u30AF\u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
"sourceLinks.unsupportedFileUri": "file URI \u306F\u672A\u5BFE\u5FDC\u3067\u3059",
"sourceLinks.useFilesystemPath": "file URI \u3067\u306F\u306A\u304F\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0\u306E\u30D1\u30B9\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
"sourceLinks.unsupportedSourceRoot": "source root \u304C\u672A\u5BFE\u5FDC\u3067\u3059",
"sourceLinks.outsideSourceRoot": "source root \u306E\u5916\u3067\u3059",
"sourceLinks.localSourceRootNotConfigured": "Local source root \u304C\u672A\u8A2D\u5B9A\u3067\u3059",
"sourceLinks.missing": "\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
"sourceLinks.available": "\u5229\u7528\u53EF\u80FD",
"sourceLinks.availableDirectory": "\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA",
"sourceLinks.uncPathNote": "UNC/WSL \u30D1\u30B9\u3067\u3059\u3002\u958B\u304F\u64CD\u4F5C\u306F OS \u3084\u30A2\u30D7\u30EA\u306E\u5BFE\u5FDC\u72B6\u6CC1\u306B\u4F9D\u5B58\u3057\u307E\u3059\u3002",
"sourceLinks.openUnavailable": "Source Link \u3092\u958B\u3051\u307E\u305B\u3093\u3067\u3057\u305F\u3002OS \u306E open \u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002",
"sourceLinks.openFailed": "Source Link \u3092\u958B\u3051\u307E\u305B\u3093\u3067\u3057\u305F: {message}",
"domains.field.type": "type",
"domains.field.id": "id",
"domains.field.name": "name",
"domains.field.kind": "kind",
"domains.field.parent": "parent",
"domains.field.description": "description",
"domains.field.path": "path",
"domains.relationship.definedIn": "\u5B9A\u7FA9\u30D5\u30A1\u30A4\u30EB",
"domains.relationship.conflicts": "\u4E0D\u6574\u5408",
"domains.relationship.dfdLocalDomains": "DFD\u5185 Domain \u304B\u3089\u306E\u53C2\u7167",
"domains.relationship.dfdObjects": "DFD object \u304B\u3089\u306E\u53C2\u7167",
"domains.relationship.parent": "\u89AA",
"domains.relationship.children": "\u5B50",
"domains.relationship.none": "\u306A\u3057",
"domains.relationship.conflictField": "{field} \u304C\u8907\u6570\u306E Domains \u30D5\u30A1\u30A4\u30EB\u3067\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093",
"domains.value.none": "-",
"domainDiagram.preview.sources": "Domain Sources",
"domainDiagram.preview.noSources": "Domain Source \u306F\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
"domainDiagram.preview.conflicts": "Domain Source \u306E\u4E0D\u6574\u5408",
"domainDiagram.preview.noConflicts": "Domain Source \u306E\u4E0D\u6574\u5408\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"domainDiagram.preview.sourceCount": "Domain Source \u6570",
"appProcess.preview.domainSourcesPlacement": "Domain Sources / \u914D\u7F6E",
"appProcess.preview.legacyLaneLayoutOnly": "legacy lane \u914D\u7F6E\u306F layout-only \u3067\u3059\u3002steps.domain \u304C\u7A7A\u306E\u5834\u5408\u3060\u3051\u4F7F\u308F\u308C\u307E\u3059\u3002",
"appProcess.preview.localDomains": "\u30ED\u30FC\u30AB\u30EB Domains",
"appProcess.preview.domainPlacement": "Domain \u914D\u7F6E",
"dfd.preview.displayedObjects": "\u8868\u793A\u4E2D\u306E\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8",
"dfd.preview.displayedFlows": "\u8868\u793A\u4E2D\u306E\u30D5\u30ED\u30FC",
"dfd.preview.noObjects": "\u63CF\u753B\u306B\u4F7F\u308F\u308C\u3066\u3044\u308B\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"dfd.preview.noFlows": "\u63CF\u753B\u306B\u4F7F\u308F\u308C\u3066\u3044\u308B\u30D5\u30ED\u30FC\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
"dfd.preview.domainPlacement": "Domain \u914D\u7F6E",
"dfd.preview.resolved": "\u89E3\u6C7A\u6E08\u307F",
"dfd.preview.unresolved": "\u672A\u89E3\u6C7A",
"domainDiagram.field.ref": "ref",
"domainDiagram.field.status": "\u72B6\u614B",
"domainDiagram.field.notes": "notes",
"domainDiagram.field.conflict": "\u4E0D\u6574\u5408",
"domainDiagram.field.earlier": "\u524D\u306E\u5B9A\u7FA9",
"domainDiagram.field.later": "\u5F8C\u306E\u5B9A\u7FA9",
"domainDiagram.field.effective": "\u6709\u52B9\u306A\u5B9A\u7FA9",
"domainDiagram.status.ok": "OK",
"domainDiagram.status.unresolved": "\u672A\u89E3\u6C7A",
"domainDiagram.status.invalid-type": "type \u4E0D\u4E00\u81F4",
"domainDiagram.status.empty": "\u7A7A",
"colorScheme.preview.title": "Color Scheme",
"colorScheme.preview.applied": "\u9069\u7528\u4E2D\u306E Color Scheme",
"colorScheme.preview.builtIn": "\u7D44\u307F\u8FBC\u307F\u65E2\u5B9A",
"colorScheme.preview.configured": "\u8A2D\u5B9A\u3055\u308C\u305F Color Scheme",
"colorScheme.preview.colors": "Colors",
"colorScheme.preview.swatch": "\u30AB\u30E9\u30FC\u78BA\u8A8D",
"colorScheme.preview.compactSwatch": "Preview",
"colorScheme.preview.compactNotes": "Notes",
"colorScheme.preview.compactSource": "Source",
"colorScheme.preview.targets": "\u5BFE\u8C61",
"colorScheme.preview.empty": "Color \u306F\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
"colorScheme.preview.blankColor": "\uFF08\u7A7A\uFF09",
"colorScheme.preview.unsupportedColor": "\u672A\u5BFE\u5FDC\u306E\u8272\u5024\u3067\u3059\u3002\u8272\u3092\u9078\u3076\u3068 #RRGGBB \u3067\u7F6E\u304D\u63DB\u3048\u307E\u3059\u3002",
"colorScheme.preview.pick": "\u9078\u629E",
"colorScheme.preview.pickAria": "{column} \u306E\u8272\u3092\u9078\u629E",
"colorScheme.preview.pickApplied": "\u8272\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F\u3002",
"colorScheme.preview.pickFailed": "\u8272\u30BB\u30EB\u3092\u66F4\u65B0\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
"colorScheme.preview.pickUnchanged": "\u8272\u306F\u3059\u3067\u306B\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u3059\u3002",
"colorScheme.field.target": "\u5BFE\u8C61",
"colorScheme.field.kind": "kind",
"colorScheme.field.fill": "\u5857\u308A",
"colorScheme.field.stroke": "\u7DDA",
"colorScheme.field.text": "\u6587\u5B57",
"colorScheme.field.notes": "notes",
"colorScheme.field.source": "source",
"settings.section.viewer": "\u30D3\u30E5\u30FC\u30A2\u30FC",
"settings.defaultClassRenderMode.name": "Class \u306E\u521D\u671F render_mode",
"settings.defaultClassRenderMode.desc": "class \u3068 class_diagram \u30D5\u30A1\u30A4\u30EB\u3067 frontmatter.render_mode \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultErRenderMode.name": "ER \u306E\u521D\u671F render_mode",
"settings.defaultErRenderMode.desc": "er_entity \u3068 er_diagram \u30D5\u30A1\u30A4\u30EB\u3067 frontmatter.render_mode \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultDfdRenderMode.name": "DFD \u306E\u521D\u671F render_mode",
"settings.defaultDfdRenderMode.desc": "dfd_diagram \u30D5\u30A1\u30A4\u30EB\u3067 frontmatter.render_mode \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultProcessRenderMode.name": "Process \u306E\u521D\u671F render_mode",
"settings.defaultProcessRenderMode.desc": "app_process \u30D5\u30A1\u30A4\u30EB\u3067 frontmatter.render_mode \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultBusinessFlowDirection.name": "Business Flow \u306E\u521D\u671F\u65B9\u5411",
"settings.defaultBusinessFlowDirection.desc": "app_process Business Flow preview \u3067 frontmatter.flow_direction \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultBusinessFlowDirection.lr": "\u5DE6\u304B\u3089\u53F3",
"settings.defaultBusinessFlowDirection.td": "\u4E0A\u304B\u3089\u4E0B",
"settings.defaultFlowDiagramViewMode.name": "Flow Diagram \u306E\u521D\u671F\u8868\u793A\u30E2\u30FC\u30C9",
"settings.defaultFlowDiagramViewMode.desc": "Flow Diagram \u3067 frontmatter.flow_view \u304C\u672A\u8A2D\u5B9A\u307E\u305F\u306F\u4E0D\u6B63\u306A\u5834\u5408\u306E\u521D\u671F\u8868\u793A\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultScreenRenderMode.name": "Screen \u306E\u521D\u671F render_mode",
"settings.defaultScreenRenderMode.desc": "screen \u30D5\u30A1\u30A4\u30EB\u3067 frontmatter.render_mode \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultDomainsViewMode.name": "Domains \u306E\u521D\u671F\u8868\u793A\u30E2\u30FC\u30C9",
"settings.defaultDomainsViewMode.desc": "domains \u30D5\u30A1\u30A4\u30EB\u3067 frontmatter.render_mode \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultDomainDiagramViewMode.name": "Domain Diagram \u306E\u521D\u671F\u8868\u793A\u30E2\u30FC\u30C9",
"settings.defaultDomainDiagramViewMode.desc": "domain_diagram \u30D5\u30A1\u30A4\u30EB\u3067 frontmatter.render_mode \u304C\u672A\u8A2D\u5B9A\u306E\u5834\u5408\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
"settings.defaultZoom.name": "\u521D\u671F\u30BA\u30FC\u30E0",
"settings.defaultZoom.desc": "\u4FDD\u5B58\u6E08\u307F viewport state \u304C\u306A\u3044\u5834\u5408\u306E diagram zoom \u3067\u3059\u3002Fit \u306F\u5168\u4F53\u8868\u793A\u3001100% \u306F\u7B49\u500D\u3067\u958B\u304D\u307E\u3059\u3002",
"settings.fontSize.name": "\u6587\u5B57\u30B5\u30A4\u30BA",
"settings.fontSize.desc": "\u30D3\u30E5\u30FC\u30A2\u30FC\u5168\u4F53\u306E\u57FA\u672C preview \u6587\u5B57\u30B5\u30A4\u30BA\u3092\u8ABF\u6574\u3057\u307E\u3059\u3002",
"settings.nodeDensity.name": "\u30CE\u30FC\u30C9\u5BC6\u5EA6",
"settings.nodeDensity.desc": "\u5BFE\u5FDC diagram \u306E\u5BC6\u5EA6\u3092\u8ABF\u6574\u3057\u307E\u3059\u3002Compact \u306F\u4F59\u767D\u3068 gap \u3092\u5C0F\u3055\u304F\u3057\u3001Relaxed \u306F\u4F59\u767D\u3092\u5E83\u304F\u3057\u307E\u3059\u3002",
"settings.relationshipView.name": "\u95A2\u9023\u8868\u793A",
"settings.relationshipView.desc": "preview \u306B object-level \u306E inbound/outbound relationships \u3092\u8868\u793A\u3057\u307E\u3059\u3002\u5927\u304D\u306A vault \u3084 reverse engineering \u3067\u901F\u5EA6\u3092\u512A\u5148\u3059\u308B\u5834\u5408\u306F\u7121\u52B9\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
"settings.showMermaidRenderDebug.name": "Mermaid render debug \u3092\u8868\u793A",
"settings.showMermaidRenderDebug.desc": "Mermaid diagrams \u306E\u4E0B\u306B\u6298\u308A\u305F\u305F\u307F\u5F0F\u306E Mermaid rendering diagnostics \u3092\u8868\u793A\u3057\u307E\u3059\u3002Mermaid source \u306F\u3053\u306E\u8A2D\u5B9A\u306B\u95A2\u4FC2\u306A\u304F\u5229\u7528\u3067\u304D\u307E\u3059\u3002",
"settings.uiLanguage.name": "UI \u8A00\u8A9E",
"settings.uiLanguage.desc": "Model Weave viewer \u3068 settings \u306E caption \u8A00\u8A9E\u3067\u3059\u3002Auto \u306F\u53EF\u80FD\u306A\u5834\u5408 Obsidian \u306B\u5F93\u3044\u307E\u3059\u3002",
"settings.localSourceRoot.name": "Local source root",
"settings.localSourceRoot.desc": "Obsidian vault \u5916\u306E relative source links \u3092\u89E3\u6C7A\u3059\u308B\u305F\u3081\u306E base directory \u3067\u3059\u3002",
"settings.defaultColorScheme.name": "Default color scheme",
"settings.defaultColorScheme.desc": "\u5BFE\u5FDC diagrams \u3067\u4F7F\u3046 color_scheme \u30D5\u30A1\u30A4\u30EB\u3078\u306E vault ref \u307E\u305F\u306F path \u3067\u3059\u3002",
"settings.refreshOpenViews.name": "\u958B\u3044\u3066\u3044\u308B view \u3092\u66F4\u65B0",
"settings.refreshOpenViews.desc": "\u73FE\u5728\u306E\u8A2D\u5B9A\u3092\u4F7F\u3063\u3066\u3001\u958B\u3044\u3066\u3044\u308B preview \u3092\u518D\u63CF\u753B\u3057\u307E\u3059\u3002",
"settings.refreshOpenViews.button": "\u66F4\u65B0",
"settings.refreshOpenViews.notice": "\u958B\u3044\u3066\u3044\u308B view \u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
"settings.option.auto": "Auto",
"settings.option.english": "English",
"settings.option.japanese": "\u65E5\u672C\u8A9E",
"settings.option.custom": "Custom",
"settings.option.mermaid": "Mermaid",
"settings.option.mermaidDetail": "Mermaid detail",
"settings.option.fit": "Fit",
"settings.option.small": "Small",
"settings.option.normal": "Normal",
"settings.option.large": "Large",
"settings.option.compact": "Compact",
"settings.option.relaxed": "Relaxed"
};
// src/i18n/messages.ts
var DICTIONARIES = {
en: EN_MESSAGES,
ja: JA_MESSAGES
};
function createModelWeaveTranslator(language) {
const dictionary = DICTIONARIES[resolveModelWeaveUiLanguage(language)];
return (key, params) => {
const template = dictionary[key] ?? EN_MESSAGES[key] ?? key;
return interpolateMessage(template, params);
};
}
function resolveModelWeaveUiLanguage(language) {
return language === "auto" ? getModelWeaveLanguage() : resolveModelWeaveLanguage(language);
}
function interpolateMessage(template, params) {
if (!params) {
return template;
}
return template.replace(/\{([A-Za-z0-9_]+)\}/g, (match, key) => {
const value = params[key];
return value === void 0 ? match : String(value);
});
}
// src/renderers/source-links-renderer.ts
function renderSourceLinks(sourceLinks, localSourceRoot, language = "auto") {
const validSourceLinks = (sourceLinks ?? []).filter(
(sourceLink) => sourceLink.path.trim()
);
const t = createModelWeaveTranslator(language);
const section = activeDocument.createElement("section");
section.addClass("model-weave-source-links");
section.addClass("model-weave-preview-section");
const title = activeDocument.createElement("h3");
title.textContent = t("sourceLinks.title");
title.addClass("model-weave-source-links-title");
title.addClass("model-weave-preview-section-title");
section.appendChild(title);
const statuses = validSourceLinks.map(
(sourceLink) => resolveSourceLinkStatus(sourceLink, localSourceRoot, t)
);
const copyEntries = validSourceLinks.map((sourceLink, index) => ({
sourceLink,
status: statuses[index]
}));
renderSourceLinksSummary(section, statuses, t);
renderSourceLinksBulkActions(section, copyEntries, t);
if (validSourceLinks.length === 0) {
section.createEl("p", {
text: t("sourceLinks.noLinks"),
cls: "model-weave-source-links-help"
});
return section;
}
section.createEl("p", {
text: t("sourceLinks.help"),
cls: "model-weave-source-links-help"
});
const tableWrap = activeDocument.createElement("div");
tableWrap.addClass("model-weave-table-wrap");
const table = activeDocument.createElement("table");
table.addClass("model-weave-source-links-table");
table.addClass("model-weave-data-table");
const thead = table.createEl("thead");
const headRow = thead.createEl("tr");
for (const header of [
t("sourceLinks.path"),
t("sourceLinks.status"),
t("sourceLinks.resolvedPath"),
t("sourceLinks.notes"),
t("sourceLinks.action")
]) {
headRow.createEl("th", {
text: header,
cls: "model-weave-source-links-th"
});
}
const tbody = table.createEl("tbody");
validSourceLinks.forEach((sourceLink, index) => {
const status = statuses[index];
const row = tbody.createEl("tr");
row.createEl("td", {
text: sourceLink.path,
cls: "model-weave-source-links-td model-weave-source-links-path"
});
const statusCell = row.createEl("td", { cls: "model-weave-source-links-td" });
const badge = statusCell.createEl("span", {
text: status.label,
cls: `model-weave-source-links-status ${status.modifierClass}`
});
badge.title = status.label;
row.createEl("td", {
text: status.resolvedPath,
cls: "model-weave-source-links-td model-weave-source-links-resolved"
});
row.createEl("td", {
text: sourceLink.notes ?? sourceLink.label ?? "-",
cls: "model-weave-source-links-td"
});
const actionCell = row.createEl("td", { cls: "model-weave-source-links-td" });
const copyButton = actionCell.createEl("button", {
text: t("sourceLinks.copyPath"),
cls: "model-weave-source-links-open"
});
copyButton.type = "button";
copyButton.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
void navigator.clipboard?.writeText(status.resolvedPath);
});
const button = actionCell.createEl("button", {
text: t("sourceLinks.open"),
cls: "model-weave-source-links-open"
});
button.type = "button";
button.disabled = !status.openable;
button.title = t("sourceLinks.openWithDefaultApp");
button.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
void openResolvedSourcePath(status.resolvedPath, t);
});
if (status.actionNote) {
actionCell.createEl("span", {
text: status.actionNote,
cls: "model-weave-source-links-action-note"
});
}
});
tableWrap.appendChild(table);
section.appendChild(tableWrap);
return section;
}
function renderSourceLinksSummary(section, statuses, t) {
const counts = {
total: statuses.length,
available: statuses.filter((status) => status.kind === "available").length,
missing: statuses.filter((status) => status.kind === "missing").length,
rootNotConfigured: statuses.filter((status) => status.kind === "root-not-configured").length
};
const summary = section.createDiv({ cls: "model-weave-source-links-summary" });
renderSourceLinksSummaryChip(summary, t("sourceLinks.summary.total"), counts.total);
renderSourceLinksSummaryChip(summary, t("sourceLinks.summary.available"), counts.available, counts.available > 0 ? "available" : void 0);
renderSourceLinksSummaryChip(summary, t("sourceLinks.summary.missing"), counts.missing, counts.missing > 0 ? "missing" : void 0);
renderSourceLinksSummaryChip(
summary,
t("sourceLinks.summary.rootNotConfigured"),
counts.rootNotConfigured,
counts.rootNotConfigured > 0 ? "warning" : void 0
);
}
function renderSourceLinksSummaryChip(container, label, value, modifier) {
const chip = container.createDiv({ cls: "model-weave-source-links-summary-chip" });
if (modifier) {
chip.addClass(`model-weave-source-links-summary-chip-${modifier}`);
}
chip.createSpan({ text: label, cls: "model-weave-source-links-summary-label" });
chip.createSpan({ text: String(value), cls: "model-weave-source-links-summary-value" });
}
function renderSourceLinksBulkActions(section, entries, t) {
const actions = section.createDiv({ cls: "model-weave-source-links-bulk-actions" });
appendBulkCopyButton(
actions,
t("sourceLinks.copyAllPaths"),
entries.map((entry) => entry.sourceLink.path)
);
appendBulkCopyButton(
actions,
t("sourceLinks.copyAvailablePaths"),
entries.filter((entry) => entry.status.kind === "available").map((entry) => entry.status.resolvedPath || entry.sourceLink.path)
);
appendBulkCopyButton(
actions,
t("sourceLinks.copyAsMarkdown"),
entries.map((entry) => formatSourceLinkMarkdownLine(entry.sourceLink))
);
appendBulkCopyButton(
actions,
t("sourceLinks.copyMissingPaths"),
entries.filter((entry) => entry.status.kind === "missing").map((entry) => entry.sourceLink.path)
);
}
function appendBulkCopyButton(container, label, lines) {
const button = container.createEl("button", {
text: label,
cls: "model-weave-source-links-bulk-copy"
});
button.type = "button";
button.disabled = lines.length === 0;
button.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
if (lines.length === 0) {
return;
}
void navigator.clipboard?.writeText(lines.join("\n"));
});
}
function formatSourceLinkMarkdownLine(sourceLink) {
const note = (sourceLink.notes ?? sourceLink.label ?? "").replace(/\s+/g, " ").trim();
return note ? `- ${sourceLink.path} \u2014 ${note}` : `- ${sourceLink.path}`;
}
function resolveSourceLinkStatus(sourceLink, localSourceRoot, t) {
const resolved = resolveSourceLinkPath(localSourceRoot, sourceLink.path);
if (resolved.kind === "fileUri") {
return {
kind: "neutral",
label: t("sourceLinks.unsupportedFileUri"),
modifierClass: "model-weave-source-links-status-neutral",
resolvedPath: resolved.resolvedPath,
openable: false,
actionNote: t("sourceLinks.useFilesystemPath")
};
}
const { kind, rootPath, resolvedPath } = resolved;
if (resolved.unsupportedSourceRoot) {
return {
kind: "neutral",
label: t("sourceLinks.unsupportedSourceRoot"),
modifierClass: "model-weave-source-links-status-neutral",
resolvedPath,
openable: true,
actionNote: getPathKindNote(kind, t)
};
}
if (resolved.usedSourceRoot && !isResolvedPathInsideRoot(kind, rootPath, resolvedPath)) {
return {
kind: "neutral",
label: t("sourceLinks.outsideSourceRoot"),
modifierClass: "model-weave-source-links-status-neutral",
resolvedPath,
openable: true
};
}
const unconfiguredRelative = kind === "relative" && !resolved.usedSourceRoot && !localSourceRoot.trim();
if (!sourcePathExists(resolvedPath)) {
return {
kind: unconfiguredRelative ? "root-not-configured" : "missing",
label: unconfiguredRelative ? t("sourceLinks.localSourceRootNotConfigured") : t("sourceLinks.missing"),
modifierClass: unconfiguredRelative ? "model-weave-source-links-status-neutral" : "model-weave-source-links-status-missing",
resolvedPath,
openable: true,
actionNote: getPathKindNote(kind, t)
};
}
const stats = (0, import_fs.statSync)(resolvedPath);
return {
kind: "available",
label: stats.isFile() ? t("sourceLinks.available") : t("sourceLinks.availableDirectory"),
modifierClass: "model-weave-source-links-status-available",
resolvedPath,
openable: true,
actionNote: getPathKindNote(kind, t)
};
}
function classifySourceRootPath(input) {
const trimmed = input.trim();
if (/^file:\/\//i.test(trimmed)) {
return {
kind: "fileUri",
normalizedPath: trimmed
};
}
if (/^\/\/[^/]+\/[^/]+/.test(trimmed)) {
return {
kind: "slashStyleWindowsUnc",
normalizedPath: `\\\\${trimmed.slice(2).replace(/\//g, "\\")}`
};
}
if (/^\\\\[^\\]+\\[^\\]+/.test(trimmed)) {
return {
kind: "windowsUnc",
normalizedPath: trimmed
};
}
if (/^[A-Za-z]:[\\/]/.test(trimmed)) {
return {
kind: "windowsDrive",
normalizedPath: import_path.default.win32.normalize(trimmed)
};
}
if (trimmed.startsWith("/")) {
return {
kind: "posixAbsolute",
normalizedPath: import_path.default.posix.normalize(trimmed)
};
}
return {
kind: "relative",
normalizedPath: trimmed
};
}
function resolveSourceLinkPath(sourceRoot, sourceLinkPath) {
const linkPath = classifySourceRootPath(sourceLinkPath);
if (linkPath.kind !== "relative") {
return {
kind: linkPath.kind === "slashStyleWindowsUnc" ? "windowsUnc" : linkPath.kind,
rootPath: "",
resolvedPath: linkPath.normalizedPath,
usedSourceRoot: false
};
}
const classified = classifySourceRootPath(sourceRoot);
if (!sourceRoot.trim() || classified.kind === "relative" || classified.kind === "fileUri") {
return {
kind: "relative",
rootPath: "",
resolvedPath: sourceLinkPath,
usedSourceRoot: false,
unsupportedSourceRoot: Boolean(sourceRoot.trim())
};
}
const cleanedRelativePath = sourceLinkPath.replace(/^[/\\]+/, "");
const pathApi = classified.kind === "windowsDrive" || classified.kind === "windowsUnc" || classified.kind === "slashStyleWindowsUnc" ? import_path.default.win32 : import_path.default.posix;
const rootPath = classified.normalizedPath;
return {
kind: classified.kind === "slashStyleWindowsUnc" ? "windowsUnc" : classified.kind,
rootPath,
resolvedPath: pathApi.normalize(pathApi.join(rootPath, cleanedRelativePath)),
usedSourceRoot: true
};
}
function isResolvedPathInsideRoot(kind, rootPath, resolvedPath) {
const pathApi = kind === "windowsDrive" || kind === "windowsUnc" ? import_path.default.win32 : import_path.default.posix;
const relativePath = pathApi.relative(rootPath, resolvedPath);
return relativePath === "" || !relativePath.startsWith("..") && !pathApi.isAbsolute(relativePath);
}
function sourcePathExists(resolvedPath) {
try {
return (0, import_fs.existsSync)(resolvedPath);
} catch {
return false;
}
}
function isUncPathKind(kind) {
return kind === "windowsUnc" || kind === "slashStyleWindowsUnc";
}
function getPathKindNote(kind, t) {
return isUncPathKind(kind) ? t("sourceLinks.uncPathNote") : void 0;
}
async function openResolvedSourcePath(resolvedPath, t) {
try {
if (typeof import_electron.shell.openPath !== "function") {
new import_obsidian6.Notice(t("sourceLinks.openUnavailable"));
return;
}
const result = await import_electron.shell.openPath(resolvedPath);
if (result) {
new import_obsidian6.Notice(t("sourceLinks.openFailed", { message: result }));
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
new import_obsidian6.Notice(t("sourceLinks.openFailed", { message }));
}
}
// src/renderers/object-renderer.ts
function renderObjectModel(model, context, localSourceRoot = "", language = "auto", options = {}) {
const root = activeDocument.createElement("section");
root.addClass("model-weave-object-focus");
root.addClass("model-weave-summary-details");
root.addClass("model-weave-preview-section");
const title = activeDocument.createElement("h2");
title.textContent = getPrimaryTitle(model);
title.addClass("model-weave-object-title");
title.addClass("model-weave-preview-section-title");
root.appendChild(title);
const meta = activeDocument.createElement("div");
meta.addClass("model-weave-object-meta");
meta.addClass("model-weave-detail-card");
if (model.fileType === "er-entity") {
appendMeta(meta, "Logical Name", model.logicalName);
appendMeta(meta, "Physical Name", model.physicalName);
appendMeta(meta, "Type", "er_entity");
appendMeta(meta, "Schema Name", model.schemaName ?? "-");
appendMeta(meta, "DBMS", model.dbms ?? "-");
appendMeta(meta, "Related Count", String(context?.relatedObjects.length ?? 0));
} else if (model.fileType === "object") {
appendMeta(meta, "Name", model.name);
appendMeta(meta, "Type", "class");
appendMeta(meta, "Kind", model.kind);
appendMeta(meta, "Related Count", String(context?.relatedObjects.length ?? 0));
} else {
appendMeta(meta, "Name", model.name);
appendMeta(meta, "Type", "dfd_object");
appendMeta(meta, "Kind", model.kind);
}
root.appendChild(meta);
if (options.includeSourceLinks !== false) {
const sourceLinks = renderSourceLinks(model.sourceLinks, localSourceRoot, language);
if (sourceLinks) {
root.appendChild(sourceLinks);
}
}
return root;
}
function getPrimaryTitle(model) {
return model.fileType === "er-entity" ? model.logicalName : model.name;
}
function appendMeta(container, label, value) {
const row = activeDocument.createElement("div");
row.addClass("model-weave-detail-card-row");
const key = activeDocument.createElement("div");
key.textContent = label;
key.addClass("model-weave-object-meta-key");
key.addClass("model-weave-detail-card-label");
const val = activeDocument.createElement("div");
val.textContent = value;
val.addClass("model-weave-object-meta-val");
val.addClass("model-weave-detail-card-value");
row.append(key, val);
container.appendChild(row);
}
// src/renderers/domains-mermaid.ts
function renderDomainsMermaidDiagram(domains, options) {
const shell3 = createMermaidShell({
className: "model-weave-domains-mermaid",
title: options.title,
forExport: options.forExport === true,
onExportPng: options.onExportPng,
onExportAndOpenPng: options.onExportAndOpenPng,
exportPngLabel: options.exportPngLabel,
exportPngTitle: options.exportPngTitle,
exportAndOpenPngLabel: options.exportAndOpenPngLabel,
exportAndOpenPngTitle: options.exportAndOpenPngTitle
});
const mode = options.mode ?? "area";
shell3.root.addClass(`model-weave-domains-mermaid-mode-${mode}`);
const interactionTargets = buildDomainsMermaidInteractionTargets(
domains,
mode,
options.interactionSourcePath ?? ""
);
const ready = renderMermaidSourceIntoShell(shell3, {
source: buildDomainsMermaidSource(domains, mode, options.colorScheme),
renderIdPrefix: getDomainsMermaidRenderIdPrefix(mode),
fitHorizontalAlign: "left",
fitVerticalAlign: options.fitVerticalAlign,
minZoom: 0.08,
minFitScale: 0.08,
viewportState: options.viewportState,
onViewportStateChange: options.onViewportStateChange,
staticRender: options.forExport === true,
showSourcePanel: options.forExport === true ? false : void 0,
sourcePanelContainer: options.sourcePanelContainer,
sourcePanelPlacement: options.sourcePanelPlacement,
sourcePanelTitle: options.sourcePanelTitle,
sourcePanelCopyLabel: options.sourcePanelCopyLabel,
showRenderDebug: options.forExport === true ? false : options.showMermaidRenderDebug === true
}).then(() => {
if (options.forExport !== true && options.app && interactionTargets.length > 0) {
attachMermaidNodeInteractions({
app: options.app,
rootEl: shell3.surface,
targets: interactionTargets,
source: "model-weave",
nodeClassName: "model-weave-mermaid-interactive-node",
dragThreshold: 6,
isDebugEnabled: () => options.showMermaidRenderDebug === true,
debugName: "Domains Mermaid",
formatTitle: (target) => target.label ? `${target.label} (${target.targetType ?? "model"})` : target.linktext
});
}
}).catch(() => {
shell3.root.addClass("model-weave-mermaid-fallback-shell");
shell3.canvas.replaceChildren(
createMermaidFallbackNotice(
options.renderFailedMessage ?? modelWeaveText(
"Domain hierarchy diagram could not be rendered.",
"Domain \u968E\u5C64\u56F3\u3092\u63CF\u753B\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002"
)
)
);
});
setMermaidRenderReadyPromise(shell3.root, ready);
return shell3.root;
}
function buildDomainsMermaidInteractionTargets(domains, mode, sourcePath) {
if (!sourcePath) {
return [];
}
if (mode === "mindmap") {
return domains.map((domain) => ({
mermaidId: toDomainMermaidId(domain.id),
linktext: sourcePath,
sourcePath,
label: getDomainMindmapLabel(domain),
kind: "domain-node",
targetType: "domain",
filePath: sourcePath,
modelId: domain.id,
modelType: "domain"
}));
}
const idMap = createDomainMermaidIds(domains);
return domains.map((domain) => ({
mermaidId: idMap.get(domain) ?? toDomainMermaidId(domain.id),
linktext: sourcePath,
sourcePath,
label: getDomainMermaidLabel(domain),
kind: "domain-node",
targetType: "domain",
filePath: sourcePath,
modelId: domain.id,
modelType: "domain"
}));
}
function buildDomainsMermaidSource(domains, mode, colorScheme) {
if (mode === "mindmap") {
return buildDomainMindmapMermaid(domains);
}
if (mode === "tree") {
return buildDomainTreeViewMermaid(domains, colorScheme);
}
return buildDomainHierarchyMermaid(domains, colorScheme);
}
function getDomainsMermaidRenderIdPrefix(mode) {
if (mode === "mindmap") {
return "model_weave_domains_mindmap";
}
if (mode === "tree") {
return "model_weave_domains_tree";
}
return "model_weave_domains";
}
function buildDomainHierarchyMermaid(domains, colorScheme) {
const roots = buildDomainTree(domains);
const idMap = createDomainMermaidIds(domains);
const lines = ["flowchart TB", ""];
const nodeStyles = [];
for (const root of roots) {
appendDomainNodeLines(lines, root, idMap, 0, colorScheme, nodeStyles);
}
if (nodeStyles.length > 0) {
lines.push("", ...nodeStyles);
}
return lines.join("\n").trimEnd();
}
function buildDomainTreeViewMermaid(domains, colorScheme) {
const roots = buildDomainTree(domains);
const idMap = createDomainMermaidIds(domains);
const lines = ["flowchart TB"];
const edges = [];
const colorClasses = /* @__PURE__ */ new Map();
const nodeClasses = [];
for (const domain of domains) {
const mermaidId = idMap.get(domain) ?? toDomainMermaidId(domain.id);
const label = escapeDomainMermaidLabel(getDomainMermaidLabel(domain));
lines.push(` ${mermaidId}["${label}"]`);
if (colorScheme) {
const className = toDomainColorClassName(domain.kind);
colorClasses.set(
className,
resolveColorStyle(colorScheme, "domain", domain.kind)
);
nodeClasses.push(` class ${mermaidId} ${className}`);
}
}
for (const root of roots) {
appendDomainTreeViewEdgeLines(edges, root, idMap, /* @__PURE__ */ new Set());
}
if (edges.length > 0) {
lines.push("", ...edges);
}
if (colorClasses.size > 0) {
lines.push("");
for (const [className, style] of colorClasses) {
lines.push(` classDef ${className} ${formatMermaidClassDefStyle3(style)}`);
}
lines.push("", ...nodeClasses);
}
return lines.join("\n").trimEnd();
}
function buildDomainMindmapMermaid(domains) {
const roots = buildDomainTree(domains);
const lines = ["mindmap"];
if (roots.length === 0) {
return lines.join("\n");
}
if (roots.length === 1) {
appendDomainMindmapRootLines(lines, roots[0]);
return lines.join("\n");
}
lines.push(" root((Domains))");
for (const root of roots) {
appendDomainMindmapNodeLines(lines, root, 2, /* @__PURE__ */ new Set());
}
return lines.join("\n");
}
function appendDomainMindmapRootLines(lines, root) {
lines.push(` root((${escapeDomainMindmapLabel(getDomainMindmapLabel(root.domain))}))`);
const visited = /* @__PURE__ */ new Set([root.domain.id]);
for (const child of root.children) {
appendDomainMindmapNodeLines(lines, child, 2, visited);
}
}
function appendDomainMindmapNodeLines(lines, node, depth, visited) {
if (visited.has(node.domain.id)) {
return;
}
const indent = " ".repeat(depth);
lines.push(`${indent}${escapeDomainMindmapLabel(getDomainMindmapLabel(node.domain))}`);
const nextVisited = new Set(visited);
nextVisited.add(node.domain.id);
for (const child of node.children) {
appendDomainMindmapNodeLines(lines, child, depth + 1, nextVisited);
}
}
function appendDomainTreeViewEdgeLines(lines, node, idMap, visited) {
if (visited.has(node.domain.id)) {
return;
}
const parentId = idMap.get(node.domain) ?? toDomainMermaidId(node.domain.id);
const nextVisited = new Set(visited);
nextVisited.add(node.domain.id);
for (const child of node.children) {
const childId = idMap.get(child.domain) ?? toDomainMermaidId(child.domain.id);
lines.push(` ${parentId} --> ${childId}`);
appendDomainTreeViewEdgeLines(lines, child, idMap, nextVisited);
}
}
function appendDomainNodeLines(lines, node, idMap, depth, colorScheme, nodeStyles) {
const indent = " ".repeat(depth);
const mermaidId = idMap.get(node.domain) ?? toDomainMermaidId(node.domain.id);
const label = escapeDomainMermaidLabel(getDomainMermaidLabel(node.domain));
if (colorScheme) {
nodeStyles.push(
` style ${mermaidId} ${formatMermaidClassDefStyle3(
resolveColorStyle(colorScheme, "domain", node.domain.kind)
)}`
);
}
if (node.children.length === 0) {
lines.push(`${indent}${mermaidId}["${label}"]`);
return;
}
lines.push(`${indent}subgraph ${mermaidId}["${label}"]`);
for (const child of node.children) {
appendDomainNodeLines(lines, child, idMap, depth + 1, colorScheme, nodeStyles);
}
lines.push(`${indent}end`);
}
function createDomainMermaidIds(domains) {
const usedIds = /* @__PURE__ */ new Set();
const idMap = /* @__PURE__ */ new Map();
for (const domain of domains) {
idMap.set(
domain,
ensureUniqueMermaidId(toDomainMermaidId(domain.id), usedIds)
);
}
return idMap;
}
function toDomainMermaidId(id) {
return `domain_${sanitizeMermaidId(id)}`;
}
function toDomainColorClassName(kind) {
const suffix = kind?.trim() ? kind.trim() : "default";
return `kind_domain_${sanitizeMermaidId(suffix)}`;
}
function formatMermaidClassDefStyle3(style) {
return [
style.fill ? `fill:${style.fill}` : void 0,
style.stroke ? `stroke:${style.stroke}` : void 0,
style.text ? `color:${style.text}` : void 0
].filter((entry) => Boolean(entry)).join(",");
}
function getDomainMermaidLabel(domain) {
const label = domain.name?.trim() || domain.id;
return domain.kind?.trim() ? `${label} [${domain.kind.trim()}]` : label;
}
function getDomainMindmapLabel(domain) {
const label = domain.name?.trim() || domain.id;
return domain.kind?.trim() ? `${label}\uFF08${domain.kind.trim()}\uFF09` : label;
}
function escapeDomainMermaidLabel(value) {
return value.replace(/\r\n?/g, "\n").split("\n").map(
(line) => line.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(//g, ">")
).join("
");
}
function escapeDomainMindmapLabel(value) {
return value.replace(/\r\n?/g, "\n").replace(/\n/g, " ").replace(/\(/g, "\uFF08").replace(/\)/g, "\uFF09").replace(/\s+/g, " ").trim();
}
// src/core/color-scheme-table-editor.ts
var COLORS_SECTION_NAME = "Colors";
var COLOR_HEADERS2 = ["target", "kind", "fill", "stroke", "text", "notes"];
var HEX_RGB_PATTERN = /^#([0-9a-fA-F]{3})$/;
var HEX_RRGGBB_PATTERN = /^#[0-9a-fA-F]{6}$/;
function updateColorSchemeColorCell(markdown, request) {
const normalizedColor = normalizeHexColorForPicker(request.value);
if (!normalizedColor || !HEX_RRGGBB_PATTERN.test(normalizedColor)) {
return unchanged2(markdown, "invalid-color");
}
const columnIndex = COLOR_HEADERS2.indexOf(request.columnName);
if (columnIndex < 0) {
return unchanged2(markdown, "column-missing");
}
const lineEnding = markdown.includes("\r\n") ? "\r\n" : "\n";
const lines = markdown.split(/\r?\n/);
const sectionRange = findMarkdownSectionRange(lines, COLORS_SECTION_NAME);
if (!sectionRange) {
return unchanged2(markdown, "section-missing");
}
const tableStart = findTableStart(lines, sectionRange.start + 1, sectionRange.end);
if (tableStart === null || tableStart + 1 >= sectionRange.end) {
return unchanged2(markdown, "table-missing");
}
const headers = splitMarkdownTableRow(lines[tableStart]) ?? [];
if (!sameHeaders8(headers, [...COLOR_HEADERS2])) {
return unchanged2(markdown, "header-mismatch");
}
const separatorCells = splitMarkdownTableRow(lines[tableStart + 1]);
if (!separatorCells || separatorCells.length !== COLOR_HEADERS2.length) {
return unchanged2(markdown, "table-missing");
}
const targetLineIndex = findDataRowLineIndex(lines, tableStart + 2, sectionRange.end, request.rowIndex);
if (targetLineIndex === null) {
return unchanged2(markdown, "row-missing");
}
const ranges = getMarkdownTableCellRanges(lines[targetLineIndex]);
if (!ranges || !ranges[columnIndex]) {
return unchanged2(markdown, "column-missing");
}
const range = ranges[columnIndex];
const line = lines[targetLineIndex];
const rawCell = line.slice(range.rawStart, range.rawEnd);
const updatedLine = rawCell.trim().length === 0 ? `${line.slice(0, range.rawStart)} ${normalizedColor} ${line.slice(range.rawEnd)}` : `${line.slice(0, range.contentStart)}${normalizedColor}${line.slice(range.contentEnd)}`;
if (updatedLine === line) {
return {
changed: false,
updatedMarkdown: markdown,
status: "unchanged"
};
}
const updatedLines = [...lines];
updatedLines[targetLineIndex] = updatedLine;
return {
changed: true,
updatedMarkdown: updatedLines.join(lineEnding),
status: "updated"
};
}
function normalizeHexColorForPicker(value) {
const trimmed = value?.trim();
if (!trimmed) {
return null;
}
const rgb = trimmed.match(HEX_RGB_PATTERN);
if (rgb) {
return `#${rgb[1].split("").map((char) => `${char}${char}`).join("")}`.toLowerCase();
}
return HEX_RRGGBB_PATTERN.test(trimmed) ? trimmed.toLowerCase() : null;
}
function findMarkdownSectionRange(lines, sectionName) {
const headingPattern = /^##\s+(.+?)\s*$/;
let start = null;
for (let index = 0; index < lines.length; index += 1) {
const match = lines[index].match(headingPattern);
if (!match) {
continue;
}
if (start !== null) {
return { start, end: index };
}
if (normalizeHeadingName(match[1]) === normalizeHeadingName(sectionName)) {
start = index;
}
}
return start === null ? null : { start, end: lines.length };
}
function findTableStart(lines, start, end) {
for (let index = start; index < end; index += 1) {
if (lines[index].trim().startsWith("|")) {
return index;
}
}
return null;
}
function findDataRowLineIndex(lines, start, end, targetRowIndex) {
let currentDataRowIndex = 0;
for (let index = start; index < end; index += 1) {
if (!lines[index].trim().startsWith("|")) {
continue;
}
const values = splitMarkdownTableRow(lines[index]);
if (!values || isEmptyMarkdownTableDataRow(values)) {
continue;
}
if (currentDataRowIndex === targetRowIndex) {
return index;
}
currentDataRowIndex += 1;
}
return null;
}
function sameHeaders8(actual, expected) {
return actual.length === expected.length && actual.every((header, index) => header === expected[index]);
}
function normalizeHeadingName(value) {
return value.trim().replace(/\s+#+$/, "").trim().toLowerCase();
}
function unchanged2(markdown, status) {
return {
changed: false,
updatedMarkdown: markdown,
status
};
}
// src/views/usage-view-renderer.ts
function renderUsageViewSections(container, sections, options) {
for (const section of sections) {
renderUsageViewSection(container, section, options);
}
}
function renderUsageDetailSection(container, id, title, emptyText, details, options) {
const body = createCollapsibleSection(container, id, title, false, options);
if (details.length === 0) {
body.createEl("p", { text: emptyText, cls: "model-weave-summary-muted" });
return;
}
const list = body.createEl("ul", { cls: "model-weave-summary-list" });
for (const detail of details) {
const item = list.createEl("li", {
text: `${detail.label}${detail.meta ? ` (${detail.meta})` : ""}`
});
item.title = detail.title ?? detail.notes ?? detail.label;
}
}
function renderGroupedSourceLinkSection(container, id, title, emptyText, sourceLinks, options) {
const body = createCollapsibleSection(container, id, title, false, options);
if (sourceLinks.length === 0) {
body.createEl("p", { text: emptyText, cls: "model-weave-summary-muted" });
return;
}
const list = body.createEl("ul", { cls: "model-weave-summary-list" });
for (const sourceLink of sourceLinks) {
renderGroupedSourceLink(list, sourceLink, options);
}
}
function renderUsageViewSection(container, section, options) {
const body = createCollapsibleSection(container, section.id, section.title, false, options);
if (section.items.length === 0) {
body.createEl("p", { text: section.emptyText, cls: "model-weave-summary-muted" });
return;
}
const list = body.createEl("ul", {
cls: "model-weave-summary-list model-weave-impact-relationship-list"
});
for (const usageItem of section.items) {
renderUsageItem(list, usageItem, options);
}
}
function renderUsageItem(list, usageItem, options) {
const item = list.createEl("li", { cls: "model-weave-impact-relationship" });
const details = item.createEl("details", {
cls: "model-weave-impact-relationship-item"
});
const row = details.createEl("summary", {
cls: "model-weave-impact-relationship-summary"
});
const rowContent = row.createSpan({
cls: "model-weave-impact-relationship-summary-content"
});
rowContent.createSpan({
cls: "model-weave-impact-relationship-title",
text: usageItem.summaryText ?? `${usageItem.label} (${usageItem.type ?? "-"}; ${options.formatUsageCount(usageItem.usageCount)})`
});
const openPath = usageItem.openTargetPath ?? usageItem.path;
if (options.onOpenItem && openPath) {
const openButton = rowContent.createEl("button", {
text: options.openLabel,
cls: "model-weave-impact-open-button"
});
openButton.type = "button";
openButton.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
options.onOpenItem?.(openPath, {
openInNewLeaf: Boolean(event.ctrlKey || event.metaKey)
});
});
openButton.addEventListener("auxclick", (event) => {
if (event.button !== 1) {
return;
}
event.preventDefault();
event.stopPropagation();
options.onOpenItem?.(openPath, {
openInNewLeaf: true
});
});
}
if (usageItem.path) {
details.createDiv({
cls: "model-weave-impact-relationship-path",
text: usageItem.path
});
}
const detailList = details.createEl("ul", {
cls: "model-weave-summary-list model-weave-impact-usage-list"
});
for (const detail of usageItem.details) {
const detailItem = detailList.createEl("li", {
cls: "model-weave-impact-usage-item"
});
detailItem.createDiv({
text: detail.label,
cls: "model-weave-impact-usage-main"
});
if (detail.meta) {
detailItem.createDiv({
text: detail.meta,
cls: "model-weave-impact-usage-meta"
});
}
if (detail.notes) {
detailItem.createDiv({
text: detail.notes,
cls: "model-weave-impact-usage-meta"
});
}
detailItem.title = detail.title ?? detail.notes ?? detail.label;
}
if (usageItem.sourceLinks.length > 0) {
const linkList = details.createEl("ul", {
cls: "model-weave-summary-list model-weave-impact-source-link-list"
});
for (const link of usageItem.sourceLinks) {
renderGroupedSourceLink(linkList, link, options, options.sourceLinkLabel);
}
}
}
function renderGroupedSourceLink(list, sourceLink, options, prefix) {
const label = sourceLink.label ? `${sourceLink.label}: ` : "";
const noteSuffix = sourceLink.notes.length > 0 ? ` (${options.formatNoteCount(sourceLink.notes.length)})` : "";
const rowText = prefix ? `${prefix}: ${label}${sourceLink.path}${noteSuffix}` : `[${sourceLink.relationKind}] ${sourceLink.ownerLabel}: ${label}${sourceLink.path}${noteSuffix}`;
const item = list.createEl("li", {
cls: "model-weave-impact-source-link-group"
});
item.title = sourceLink.notes.length > 0 ? sourceLink.notes.join("\n") : sourceLink.ownerPath ?? sourceLink.path;
if (sourceLink.notes.length === 0) {
item.setText(rowText);
return;
}
const details = item.createEl("details", {
cls: "model-weave-impact-source-link-details"
});
details.createEl("summary", { text: rowText });
const noteList = details.createEl("ul", {
cls: "model-weave-summary-list model-weave-impact-source-link-note-list"
});
for (const note of sourceLink.notes) {
noteList.createEl("li", { text: note });
}
}
function createCollapsibleSection(container, key, title, defaultOpen, options) {
const details = container.createEl("details");
details.addClass("model-weave-preview-section");
details.open = options.getOpenState ? options.getOpenState(key, defaultOpen) : defaultOpen;
if (options.setOpenState) {
details.addEventListener("toggle", () => {
options.setOpenState?.(key, details.open);
});
}
const summary = details.createEl("summary", { text: title });
summary.addClass("model-weave-summary-heading");
summary.addClass("model-weave-preview-section-title");
return details.createDiv();
}
// src/views/applied-color-scheme-renderer.ts
function renderAppliedColorSchemeSectionContent(container, colorScheme, rows, targets, t) {
container.createEl("p", {
text: formatAppliedColorSchemeSummary(colorScheme, targets, t),
cls: "model-weave-summary-muted"
});
const tableWrap = container.createDiv({ cls: "model-weave-table-wrap" });
const table = tableWrap.createEl("table", {
cls: "model-weave-summary-table model-weave-data-table"
});
const headerRow = table.createEl("thead").createEl("tr");
for (const key of [
"colorScheme.field.target",
"colorScheme.field.kind",
"colorScheme.preview.compactSwatch",
"colorScheme.preview.compactNotes",
"colorScheme.preview.compactSource"
]) {
headerRow.createEl("th", {
text: t(key),
cls: "model-weave-summary-th"
});
}
const tbody = table.createEl("tbody");
if (rows.length === 0) {
const row = tbody.createEl("tr");
row.createEl("td", {
text: t("colorScheme.preview.empty"),
attr: { colspan: "5" },
cls: "model-weave-summary-muted"
});
return;
}
for (const row of rows) {
renderAppliedColorSchemeTableRow(tbody, row, t);
}
}
function formatAppliedColorSchemeSummary(colorScheme, targets, t) {
const schemeType = colorScheme.sourcePath ? t("colorScheme.preview.configured") : t("colorScheme.preview.builtIn");
const schemeName = colorScheme.sourcePath ? `${colorScheme.name} (${colorScheme.sourcePath})` : colorScheme.name;
const targetList = targets.length > 0 ? targets.join(", ") : t("domains.value.none");
return `${schemeType}: ${schemeName} / ${t("colorScheme.preview.targets")}: ${targetList}`;
}
function renderAppliedColorSchemeTableRow(tbody, row, t) {
const tableRow = tbody.createEl("tr");
const color = row.entry;
for (const value of [
color.target ?? t("domains.value.none"),
color.kind
]) {
tableRow.createEl("td", { text: value });
}
const swatchCell = tableRow.createEl("td");
const swatchTitle = formatAppliedColorSchemeSwatchTitle(row, t);
const swatch = swatchCell.createSpan({
cls: "model-weave-color-swatch",
attr: {
"aria-label": swatchTitle
}
});
if (color.fill) {
swatch.style.backgroundColor = color.fill;
}
if (color.stroke) {
swatch.style.borderColor = color.stroke;
}
if (color.text) {
swatch.style.color = color.text;
}
swatch.textContent = "Aa";
tableRow.createEl("td", { text: color.notes ?? "" });
tableRow.createEl("td", {
text: row.source === "built-in" ? t("colorScheme.preview.builtIn") : t("colorScheme.preview.configured")
});
}
function formatAppliedColorSchemeSwatchTitle(row, t) {
const color = row.entry;
return [
`fill: ${color.fill ?? t("domains.value.none")}`,
`stroke: ${color.stroke ?? t("domains.value.none")}`,
`text: ${color.text ?? t("domains.value.none")}`
].join("\n");
}
// src/views/view-icon.ts
var MODELING_VIEW_ICON = "git-branch";
// src/views/modeling-preview-view.ts
var MODELING_PREVIEW_VIEW_TYPE = "mdspec-preview";
function isDomainRenderMode(value) {
return value === "mindmap" || value === "area" || value === "tree";
}
function isStandardRenderMode(value) {
return value === "custom" || value === "mermaid" || value === "mermaid-detail";
}
function getStandardRenderMode(selection, fallback) {
const mode = selection?.effectiveMode;
return isStandardRenderMode(mode) ? mode : fallback;
}
function getDomainRenderModeFromSelection(selection) {
return isDomainRenderMode(selection?.effectiveMode) ? selection.effectiveMode : null;
}
function getMermaidSourceLabels(t) {
return {
sourcePanelTitle: t("mermaid.source.title"),
sourcePanelCopyLabel: t("mermaid.source.copy")
};
}
function getGraphExportLabels(t) {
const exportLabel = t("graph.exportPng");
const exportAndOpenLabel = t("graph.exportPngOpen");
return {
exportPngLabel: exportLabel,
exportPngTitle: exportLabel,
exportAndOpenPngLabel: exportAndOpenLabel,
exportAndOpenPngTitle: exportAndOpenLabel
};
}
function isDesktopVaultAdapter(adapter) {
return typeof adapter === "object" && adapter !== null && "getFullPath" in adapter && typeof adapter.getFullPath === "function";
}
function getDfdDetailLabels(t) {
return {
dfdDetailLabels: {
displayedObjects: t("dfd.preview.displayedObjects"),
displayedFlows: t("dfd.preview.displayedFlows"),
noObjects: t("dfd.preview.noObjects"),
noFlows: t("dfd.preview.noFlows"),
domainPlacement: t("dfd.preview.domainPlacement"),
resolved: t("dfd.preview.resolved"),
unresolved: t("dfd.preview.unresolved")
}
};
}
function getObjectContextLabels(t) {
return {
title: t("objectContext.title"),
linked: (count) => t("objectContext.linked", { count }),
connectionDetails: t("objectContext.connectionDetails"),
relationDetails: t("objectContext.relationDetails"),
noDirectlyRelated: t("objectContext.noDirectlyRelated")
};
}
function getClassDetailLabels(t) {
return {
classDetailLabels: {
displayedRelations: t("class.preview.displayedRelations"),
noRelationsUsed: t("class.preview.noRelationsUsed")
}
};
}
var nextViewerLowerPanelInstanceId = 0;
var VIEWPORT_STATE_CACHE_LIMIT = 50;
var DEFAULT_VIEWER_PREFERENCES = {
defaultZoom: "fit",
fontSize: "normal",
nodeDensity: "normal",
defaultDomainsViewMode: "mindmap",
defaultDomainDiagramViewMode: "mindmap",
defaultBusinessFlowDirection: "LR",
defaultFlowDiagramViewMode: "detail",
localSourceRoot: "",
uiLanguage: "auto",
showMermaidRenderDebug: false
};
function getAppliedColorSchemeLowerPaneSlot(_modelType) {
return "details";
}
var ModelingPreviewView = class extends import_obsidian7.ItemView {
constructor(leaf, viewerPreferences = DEFAULT_VIEWER_PREFERENCES, paneActions = {}) {
super(leaf);
this.lowerPanelDomIdPrefix = `model-weave-lower-${nextViewerLowerPanelInstanceId++}`;
this.diagramViewportState = {
zoom: 1,
panX: 0,
panY: 0,
viewMode: "fit",
hasAutoFitted: false,
hasUserInteracted: false
};
this.objectGraphViewportState = {
zoom: 1,
panX: 0,
panY: 0,
viewMode: "fit",
hasAutoFitted: false,
hasUserInteracted: false
};
this.screenPreviewViewportState = {
zoom: 1,
panX: 0,
panY: 0,
viewMode: "fit",
hasAutoFitted: false,
hasUserInteracted: false
};
this.domainsMermaidViewportState = {
zoom: 1,
panX: 0,
panY: 0,
viewMode: "fit",
hasAutoFitted: false,
hasUserInteracted: false
};
this.state = {
mode: "empty",
message: "\u5BFE\u5FDC\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F\u3068\u30D7\u30EC\u30D3\u30E5\u30FC\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002",
warnings: []
};
this.diagramFilePath = null;
this.objectGraphFilePath = null;
this.screenPreviewFilePath = null;
this.viewportStateCache = /* @__PURE__ */ new Map();
this.collapsibleState = /* @__PURE__ */ new Map();
this.scrollStateByFilePath = /* @__PURE__ */ new Map();
this.splitRatioByKey = /* @__PURE__ */ new Map();
this.domainsDiagramMode = "mindmap";
this.domainsDiagramModeFilePath = null;
this.appProcessBusinessFlowDirectionOverride = null;
this.appProcessBusinessFlowDirectionFilePath = null;
this.appProcessFlowConnectFilePath = null;
this.flowDiagramViewModes = new FlowDiagramViewModeState();
this.appProcessFlowConnectModeEnabled = false;
this.appProcessFlowConnectSourceStepId = null;
this.domainsDiagramModeState = null;
this.activeLowerPanelTabId = null;
this.activeScrollContainer = null;
this.focusModeEnabled = false;
this.focusModePlaceholder = null;
this.viewOnlyEnabled = false;
this.viewOnlyTarget = null;
this.viewOnlyPlaceholder = null;
this.viewOnlyStage = null;
this.handleFocusModeKeydown = (event) => {
if (event.key !== "Escape" || !this.focusModeEnabled) {
return;
}
event.preventDefault();
this.setFocusMode(false);
};
this.getDiagnosticQuickFixActions = (diagnostic, t) => {
const actions = [];
const frontmatterAction = this.createFrontmatterQuickFixAction(diagnostic, t);
if (frontmatterAction) {
actions.push(frontmatterAction);
}
return actions;
};
this.getCollapsibleOpenState = (key, defaultOpen) => {
return this.collapsibleState.get(key) ?? defaultOpen;
};
this.setCollapsibleOpenState = (key, open) => {
this.collapsibleState.set(key, open);
};
this.viewerPreferences = { ...viewerPreferences };
this.t = createModelWeaveTranslator(this.viewerPreferences.uiLanguage);
this.paneActions = paneActions;
}
getViewType() {
return MODELING_PREVIEW_VIEW_TYPE;
}
getDisplayText() {
return "Modeling preview";
}
getIcon() {
return MODELING_VIEW_ICON;
}
onOpen() {
this.contentEl.ownerDocument.addEventListener("keydown", this.handleFocusModeKeydown);
this.renderCurrentState();
return Promise.resolve();
}
onClose() {
this.contentEl.ownerDocument.removeEventListener("keydown", this.handleFocusModeKeydown);
this.setFocusMode(false, { skipFit: true });
this.clearView();
return Promise.resolve();
}
applyViewerSettings(viewerPreferences) {
this.viewerPreferences = { ...viewerPreferences };
this.t = createModelWeaveTranslator(this.viewerPreferences.uiLanguage);
}
refreshForSettingsChange() {
this.renderCurrentState();
this.restoreCurrentScrollPosition();
}
async exportCurrentDiagramAsPng() {
const exportRenderable = this.buildCurrentDiagramExportRenderable();
if (!exportRenderable) {
return null;
}
return exportDiagramRenderableAsPng(this.app, exportRenderable);
}
async exportCurrentDiagramAsPngWithNotice() {
try {
const exportPath = await this.exportCurrentDiagramAsPng();
if (!exportPath) {
new import_obsidian7.Notice("The current view is not ready for export.");
return;
}
new import_obsidian7.Notice(`Diagram exported: ${exportPath}`);
} catch (error) {
this.showPngExportFailureNotice(error);
}
}
async exportCurrentDiagramAsPngAndOpenWithNotice() {
try {
const exportPath = await this.exportCurrentDiagramAsPng();
if (!exportPath) {
new import_obsidian7.Notice("The current view is not ready for export.");
return;
}
new import_obsidian7.Notice(`Diagram exported: ${exportPath}`);
await this.openExportedPng(exportPath);
} catch (error) {
this.showPngExportFailureNotice(error);
}
}
async exportWeaveMapPng(container, filePath) {
const snapshot = buildDomDiagramExportSnapshot(
container,
filePath,
"weave-map"
);
if (!snapshot) {
return null;
}
return exportDiagramSnapshotAsPng(this.app, snapshot);
}
async exportWeaveMapAsPng(container, filePath) {
try {
const exportPath = await this.exportWeaveMapPng(container, filePath);
if (!exportPath) {
new import_obsidian7.Notice("The current diagram has no measurable export bounds.");
return;
}
new import_obsidian7.Notice(`Diagram exported: ${exportPath}`);
} catch (error) {
this.showPngExportFailureNotice(error);
}
}
async exportWeaveMapAsPngAndOpen(container, filePath) {
try {
const exportPath = await this.exportWeaveMapPng(container, filePath);
if (!exportPath) {
new import_obsidian7.Notice("The current diagram has no measurable export bounds.");
return;
}
new import_obsidian7.Notice(`Diagram exported: ${exportPath}`);
await this.openExportedPng(exportPath);
} catch (error) {
this.showPngExportFailureNotice(error);
}
}
async openExportedPng(exportPath) {
const adapter = this.app.vault.adapter;
if (!isDesktopVaultAdapter(adapter)) {
new import_obsidian7.Notice(this.t("graph.exportPngOpenUnavailable"));
return;
}
try {
if (typeof import_electron2.shell.openPath !== "function") {
new import_obsidian7.Notice(this.t("graph.exportPngOpenUnavailable"));
return;
}
const result = await import_electron2.shell.openPath(adapter.getFullPath(exportPath));
if (result) {
new import_obsidian7.Notice(this.t("graph.exportPngOpenFailed", { message: result }));
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
new import_obsidian7.Notice(this.t("graph.exportPngOpenFailed", { message }));
}
}
showPngExportFailureNotice(error) {
if (error instanceof DiagramExportError && error.code === "bounds-invalid") {
new import_obsidian7.Notice("The current diagram has no measurable export bounds.");
return;
}
new import_obsidian7.Notice("Failed to export the current diagram as PNG.");
}
updateContent(state, reason = "rerender") {
const previousFilePath = this.getCurrentFilePath();
const nextFilePath = this.getFilePathForState(state);
if (previousFilePath && nextFilePath && previousFilePath !== nextFilePath) {
this.resetImpactCollapsibleState();
this.activeLowerPanelTabId = null;
} else if (this.state.mode !== state.mode) {
this.activeLowerPanelTabId = null;
}
this.persistActiveViewportState();
this.persistCurrentScrollPosition();
this.prepareDomainsDiagramMode(state, nextFilePath);
this.prepareAppProcessBusinessFlowDirection(state, nextFilePath);
this.prepareAppProcessFlowConnectMode(nextFilePath);
const flowViewReinitialized = this.prepareFlowDiagramViewMode(state, nextFilePath);
if (flowViewReinitialized && nextFilePath) {
this.viewportStateCache.delete(nextFilePath);
resetGraphViewportState(this.diagramViewportState);
}
this.prepareViewportState(state, reason);
this.state = state;
this.renderCurrentState();
this.restoreCurrentScrollPosition();
}
getCurrentFilePath() {
return this.getFilePathForState(this.state);
}
createFrontmatterQuickFixAction(diagnostic, t) {
const missingField = getMissingFrontmatterKey(diagnostic) ?? getMissingRequiredFieldName(diagnostic);
if (!missingField || missingField === "type") {
return null;
}
const values = this.getSafeFrontmatterQuickFixValues(missingField);
if (!values) {
return null;
}
const label = missingField === "id" ? t("diagnostics.quickFix.insertId") : t("diagnostics.quickFix.insertMissingField");
return {
id: "quick-fix-frontmatter-" + missingField,
label,
kind: "quick-fix",
enabled: true,
futureFixType: "frontmatter",
groupLabel: t("diagnostics.quickFix.group"),
run: async () => {
await this.applyFrontmatterQuickFix(values);
}
};
}
getSafeFrontmatterQuickFixValues(missingField) {
const filePath = this.getCurrentFilePath();
if (!filePath) {
return null;
}
const basename = filePath.split(/[\\/]/).pop()?.replace(/\.md$/i, "").trim();
if (!basename) {
return null;
}
const values = {};
if (missingField === "id") {
values.id = basename;
return values;
}
if (missingField === "name") {
values.name = basename;
return values;
}
return null;
}
async applyFrontmatterQuickFix(values) {
try {
const file = this.getCurrentTFileForQuickFix();
if (!file) {
new import_obsidian7.Notice(this.t("diagnostics.quickFix.failed"));
return;
}
const markdown = await this.app.vault.read(file);
const updated = applyFrontmatterPatch(markdown, values);
if (!updated) {
new import_obsidian7.Notice(this.t("diagnostics.quickFix.failed"));
return;
}
await this.app.vault.modify(file, updated);
new import_obsidian7.Notice(this.t("diagnostics.quickFix.applied"));
this.renderCurrentState();
} catch {
new import_obsidian7.Notice(this.t("diagnostics.quickFix.failed"));
}
}
getCurrentTFileForQuickFix() {
const filePath = this.getCurrentFilePath();
if (!filePath) {
return null;
}
const file = this.app.vault.getAbstractFileByPath(filePath);
return file instanceof import_obsidian7.TFile ? file : null;
}
getFilePathForState(state) {
switch (state.mode) {
case "diagram":
return state.diagram.diagram.path;
case "object":
return "filePath" in state.model ? state.model.filePath : state.model.path;
case "dfd-object":
return state.model.path;
case "domains":
return state.model.path;
case "domain-diagram":
return state.resolved.diagram.path;
case "color-scheme":
return state.model.path;
case "summary":
return state.filePath;
default:
return null;
}
}
persistActiveViewportState() {
if (this.diagramFilePath) {
this.rememberViewportState(this.diagramFilePath, this.diagramViewportState);
}
if (this.objectGraphFilePath) {
this.rememberViewportState(this.objectGraphFilePath, this.objectGraphViewportState);
}
if (this.screenPreviewFilePath) {
this.rememberViewportState(this.screenPreviewFilePath, this.screenPreviewViewportState);
}
}
prepareViewportState(state, reason) {
if (state.mode === "diagram") {
const nextFilePath = state.diagram.diagram.path;
this.prepareFileViewportState(
this.diagramViewportState,
this.diagramFilePath,
nextFilePath,
reason
);
this.diagramFilePath = nextFilePath;
return;
}
if (state.mode === "object" && state.context) {
const objectPath = "filePath" in state.model ? state.model.filePath : state.model.path;
this.prepareFileViewportState(
this.objectGraphViewportState,
this.objectGraphFilePath,
objectPath,
reason
);
this.objectGraphFilePath = objectPath;
return;
}
if (state.mode === "dfd-object") {
this.prepareFileViewportState(
this.objectGraphViewportState,
this.objectGraphFilePath,
state.model.path,
reason
);
this.objectGraphFilePath = state.model.path;
return;
}
if (state.mode === "summary" && ((state.layoutBlocks?.length ?? 0) > 0 || (state.businessFlow?.steps.length ?? 0) > 0)) {
this.prepareFileViewportState(
this.screenPreviewViewportState,
this.screenPreviewFilePath,
state.filePath,
reason
);
this.screenPreviewFilePath = state.filePath;
return;
}
if (state.mode !== "object") {
this.objectGraphFilePath = null;
}
if (state.mode !== "summary" || (state.layoutBlocks?.length ?? 0) === 0 && (state.businessFlow?.steps.length ?? 0) === 0) {
this.screenPreviewFilePath = null;
}
this.diagramFilePath = null;
}
prepareFileViewportState(state, currentFilePath, nextFilePath, reason) {
if (reason === "manual-fit") {
return;
}
if (reason === "renderer-switch") {
this.viewportStateCache.delete(nextFilePath);
resetGraphViewportState(state);
return;
}
if (currentFilePath === nextFilePath) {
return;
}
const cached = this.viewportStateCache.get(nextFilePath);
if (cached) {
if (cached.viewMode === "fit") {
resetGraphViewportState(state);
} else {
state.zoom = cached.zoom;
state.panX = cached.panX;
state.panY = cached.panY;
state.viewMode = "manual";
state.hasAutoFitted = true;
state.hasUserInteracted = true;
}
cached.updatedAt = Date.now();
return;
}
resetGraphViewportState(state);
if (this.viewerPreferences.defaultZoom === "100") {
state.zoom = 1;
state.panX = 0;
state.panY = 0;
state.viewMode = "manual";
state.hasAutoFitted = true;
state.hasUserInteracted = false;
}
}
prepareAppProcessBusinessFlowDirection(state, nextFilePath) {
if (state.mode !== "summary" || (state.businessFlow?.steps.length ?? 0) === 0) {
this.appProcessBusinessFlowDirectionOverride = null;
this.appProcessBusinessFlowDirectionFilePath = null;
return;
}
if (this.appProcessBusinessFlowDirectionFilePath === nextFilePath) {
return;
}
this.appProcessBusinessFlowDirectionOverride = null;
this.appProcessBusinessFlowDirectionFilePath = nextFilePath;
}
prepareAppProcessFlowConnectMode(nextFilePath) {
if (this.appProcessFlowConnectFilePath === nextFilePath) {
return;
}
this.appProcessFlowConnectFilePath = nextFilePath;
this.appProcessFlowConnectModeEnabled = false;
this.appProcessFlowConnectSourceStepId = null;
}
prepareDomainsDiagramMode(state, nextFilePath) {
if (state.mode !== "domains" && state.mode !== "domain-diagram") {
this.domainsDiagramModeFilePath = null;
this.domainsDiagramModeState = null;
return;
}
if (this.domainsDiagramModeFilePath === nextFilePath && this.domainsDiagramModeState === state.mode) {
return;
}
this.domainsDiagramMode = getDomainRenderModeFromSelection(state.rendererSelection) ?? (state.mode === "domains" ? this.viewerPreferences.defaultDomainsViewMode : this.viewerPreferences.defaultDomainDiagramViewMode);
this.domainsDiagramModeFilePath = nextFilePath;
this.domainsDiagramModeState = state.mode;
}
prepareFlowDiagramViewMode(state, nextFilePath) {
if (state.mode !== "diagram" || state.diagram.diagram.schema !== "flow_diagram" || !nextFilePath) {
return false;
}
return this.flowDiagramViewModes.synchronize(
nextFilePath,
state.diagram.diagram,
this.viewerPreferences.defaultFlowDiagramViewMode
).initializationChanged;
}
rememberViewportState(filePath, state) {
if (!state.hasAutoFitted && !state.hasUserInteracted) {
return;
}
this.viewportStateCache.set(filePath, {
filePath,
viewMode: state.viewMode,
zoom: state.zoom,
panX: state.panX,
panY: state.panY,
updatedAt: Date.now()
});
this.pruneViewportStateCache();
}
pruneViewportStateCache() {
if (this.viewportStateCache.size <= VIEWPORT_STATE_CACHE_LIMIT) {
return;
}
const oldestEntries = [...this.viewportStateCache.entries()].sort(
(left, right) => left[1].updatedAt - right[1].updatedAt
);
for (const [filePath] of oldestEntries.slice(
0,
this.viewportStateCache.size - VIEWPORT_STATE_CACHE_LIMIT
)) {
this.viewportStateCache.delete(filePath);
}
}
getCurrentDiagramFilePath() {
switch (this.state.mode) {
case "diagram":
return this.state.diagram.diagram.path;
case "object":
return this.state.context ? "filePath" in this.state.model ? this.state.model.filePath : this.state.model.path : null;
case "dfd-object":
return this.state.model.path;
default:
return null;
}
}
buildCurrentDiagramExportRenderable() {
const state = this.state;
switch (state.mode) {
case "diagram":
return {
filePath: state.diagram.diagram.path,
renderer: state.rendererSelection?.effectiveMode ?? "custom",
render: () => renderDiagramModel(state.diagram, {
hideTitle: true,
hideDetails: true,
forExport: true,
renderMode: getStandardRenderMode(state.rendererSelection),
colorScheme: state.colorScheme,
flowDiagramViewMode: state.diagram.diagram.schema === "flow_diagram" ? this.getFlowDiagramViewMode(state) : void 0,
...getMermaidSourceLabels(this.t)
})
};
case "object": {
const filePath = this.getCurrentDiagramFilePath();
if (!filePath) {
return null;
}
if (state.rendererSelection?.actualRenderer === "mermaid") {
const context2 = state.context ?? {
object: state.model,
relatedObjects: [],
warnings: []
};
const subgraph2 = buildObjectSubgraphScene(context2);
return {
filePath,
renderer: state.rendererSelection?.effectiveMode ?? "mermaid",
render: () => renderDiagramModel(subgraph2, {
hideTitle: true,
hideDetails: true,
forExport: true,
fitVerticalAlign: "top",
renderMode: getStandardRenderMode(state.rendererSelection, "mermaid"),
...getMermaidSourceLabels(this.t)
})
};
}
const context = state.context ?? {
object: state.model,
relatedObjects: [],
warnings: []
};
const subgraph = buildObjectSubgraphScene(context);
return {
filePath,
renderer: state.rendererSelection?.effectiveMode ?? "custom",
render: () => renderDiagramModel(subgraph, {
hideTitle: true,
hideDetails: true,
fitVerticalAlign: "top",
forExport: true
})
};
}
case "dfd-object":
return {
filePath: state.model.path,
renderer: state.rendererSelection?.effectiveMode ?? "custom",
render: () => renderDiagramModel(state.diagram, {
hideTitle: true,
hideDetails: true,
forExport: true
})
};
case "domains":
return this.buildDomainsDiagramExportRenderable(
state.model.path,
state.model.domains,
state.colorScheme
);
case "domain-diagram":
return this.buildDomainsDiagramExportRenderable(
state.resolved.diagram.path,
state.resolved.domains,
state.colorScheme
);
case "summary":
if ((state.layoutBlocks?.length ?? 0) > 0) {
return {
filePath: state.filePath,
renderer: "custom",
render: () => createScreenPreviewDiagram(buildScreenPreviewData(state, this.t), {
forExport: true
})
};
}
if ((state.businessFlow?.steps.length ?? 0) > 0) {
return {
filePath: state.filePath,
renderer: "business-flow",
render: () => renderAppProcessBusinessFlow(state.businessFlow, {
forExport: true,
debug: false,
colorScheme: state.colorScheme,
flowDirection: this.getAppProcessBusinessFlowDirection(state)
})
};
}
return null;
default:
return null;
}
}
buildDomainsDiagramExportRenderable(filePath, domains, colorScheme) {
if (domains.length === 0) {
return null;
}
const mode = this.domainsDiagramMode;
return {
filePath,
renderer: mode,
render: () => renderDomainsMermaidDiagram(domains, {
title: this.getDomainDiagramModeLabel(mode),
mode,
renderFailedMessage: this.t("domains.preview.diagramRenderFailed"),
fitVerticalAlign: "top",
colorScheme,
forExport: true
})
};
}
createDiagramViewportStateHandler(filePath) {
return (viewportState) => {
if (this.state.mode !== "diagram" || this.diagramFilePath !== filePath || this.state.diagram.diagram.path !== filePath) {
return;
}
this.rememberViewportState(filePath, viewportState);
};
}
createObjectViewportStateHandler(filePath) {
return (viewportState) => {
if (this.state.mode !== "object" || this.objectGraphFilePath !== filePath) {
return;
}
const currentPath = "filePath" in this.state.model ? this.state.model.filePath : this.state.model.path;
if (currentPath !== filePath) {
return;
}
this.rememberViewportState(filePath, viewportState);
};
}
createScreenPreviewViewportStateHandler(filePath) {
return (viewportState) => {
if (this.state.mode !== "summary" || this.screenPreviewFilePath !== filePath || this.state.filePath !== filePath) {
return;
}
this.rememberViewportState(filePath, viewportState);
};
}
renderCurrentState() {
const shouldRestoreBusinessFlowViewOnly = this.shouldRestoreBusinessFlowViewOnly();
this.clearView();
switch (this.state.mode) {
case "object":
this.renderObjectState(this.state);
break;
case "relations":
this.renderRelationsState(this.state);
break;
case "domains":
this.renderDomainsState(this.state);
break;
case "domain-diagram":
this.renderDomainDiagramState(this.state);
break;
case "color-scheme":
this.renderColorSchemeState(this.state);
break;
case "summary":
this.renderSummaryState(this.state);
break;
case "dfd-object":
this.renderDfdObjectState(this.state);
break;
case "diagram":
this.renderDiagramState(this.state);
break;
case "empty":
default:
this.renderEmptyState(this.state.message);
break;
}
this.applyLowerPanelTabs();
this.restoreBusinessFlowViewOnlyAfterRender(shouldRestoreBusinessFlowViewOnly);
}
shouldRestoreBusinessFlowViewOnly() {
return Boolean(
this.viewOnlyEnabled && this.viewOnlyTarget?.classList.contains("model-weave-app-process-business-flow")
);
}
restoreBusinessFlowViewOnlyAfterRender(shouldRestore) {
if (!shouldRestore) {
return;
}
const view = this.contentEl.ownerDocument.defaultView;
view?.requestAnimationFrame(() => {
view.requestAnimationFrame(() => {
const nextTarget = this.contentEl.querySelector(
".model-weave-app-process-business-flow"
);
if (nextTarget) {
this.setViewOnlyMode(true, { target: nextTarget, skipFit: true });
}
});
});
}
clearView() {
this.setViewOnlyMode(false, { skipFit: true });
this.contentEl.empty();
this.activeScrollContainer = null;
this.contentEl.classList.remove(
"model-weave-viewer-root",
"mw-font-small",
"mw-font-normal",
"mw-font-large",
"mw-density-compact",
"mw-density-normal",
"mw-density-relaxed",
"model-weave-viewer-view-only"
);
this.contentEl.classList.add("model-weave-viewer-root");
this.contentEl.classList.toggle("model-weave-viewer-focus-mode", this.focusModeEnabled);
this.contentEl.classList.toggle("model-weave-viewer-view-only", this.viewOnlyEnabled);
this.contentEl.classList.add(`mw-font-${this.viewerPreferences.fontSize}`);
this.contentEl.classList.add(`mw-density-${this.viewerPreferences.nodeDensity}`);
const fontVars = this.getFontSizeVariables();
this.contentEl.setCssProps({
"--model-weave-font-size": fontVars.base,
"--model-weave-font-size-small": fontVars.small,
"--model-weave-font-size-large": fontVars.large,
"--model-weave-font-size-title": fontVars.title,
"--mw-content-gap": `${this.getDensitySpacing().contentGap}px`
});
this.appendViewerFocusToolbar();
this.ensureViewOnlyStage();
}
renderEmptyState(message) {
const doc = this.contentEl.ownerDocument;
const section = doc.createElement("section");
section.addClass("model-weave-viewer-empty");
const text = doc.createElement("p");
text.textContent = message;
text.addClass("model-weave-viewer-empty-text");
section.appendChild(text);
this.contentEl.appendChild(section);
}
renderObjectState(state) {
const objectPath = "filePath" in state.model ? state.model.filePath : state.model.path;
const shell3 = this.createViewerSplitShell(`object:${objectPath}`, 0.62);
shell3.bottomPane.addClass("model-weave-summary-details");
this.activeScrollContainer = shell3.bottomPane;
this.renderReviewSummaryPanel(shell3.bottomPane, {
model: state.model,
warnings: state.warnings,
impactSummary: state.impactSummary,
sourceLinks: state.model.sourceLinks,
weaveMapAvailable: Boolean(state.weaveMapMermaidSource)
});
renderDiagnostics(
shell3.bottomPane,
state.warnings,
state.onOpenDiagnostic ?? void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
const objectDetails = renderObjectModel(
state.model,
state.context,
this.viewerPreferences.localSourceRoot,
this.viewerPreferences.uiLanguage,
{ includeSourceLinks: false }
);
this.renderImpactSummarySection(
shell3.bottomPane,
state.impactSummary,
state.onCopyImpactSummary,
state.onOpenImpactModel,
state.weaveMapMermaidSource,
state.colorScheme
);
this.renderSourceLinksSection(shell3.bottomPane, state.model.sourceLinks);
shell3.bottomPane.appendChild(objectDetails);
if (!state.context) {
return;
}
if (state.rendererSelection?.actualRenderer === "mermaid") {
const contextRoot2 = renderObjectContext(state.context, {
onOpenObject: state.onOpenObject ?? void 0,
app: this.app,
interactionSourcePath: objectPath,
viewportState: this.objectGraphViewportState,
onViewportStateChange: this.createObjectViewportStateHandler(objectPath),
labels: getObjectContextLabels(this.t)
});
const relatedList2 = Array.from(contextRoot2.children).find(
(child) => child.instanceOf(HTMLElement) && (child.classList.contains("model-weave-object-context-list") || child.classList.contains("mdspec-related-list"))
);
if (relatedList2) {
relatedList2.remove();
shell3.bottomPane.appendChild(relatedList2);
}
const subgraph = buildObjectSubgraphScene(state.context);
const mermaidRoot = renderDiagramModel(subgraph, {
app: this.app,
interactionSourcePath: objectPath,
hideTitle: true,
hideDetails: true,
renderMode: getStandardRenderMode(state.rendererSelection, "mermaid"),
fitVerticalAlign: "top",
viewportState: this.objectGraphViewportState,
onViewportStateChange: this.createObjectViewportStateHandler(objectPath),
sourcePanelContainer: shell3.bottomPane,
sourcePanelPlacement: "prepend",
...getMermaidSourceLabels(this.t),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportCurrentDiagramAsPngWithNotice(),
onExportAndOpenPng: () => this.exportCurrentDiagramAsPngAndOpenWithNotice(),
...getDfdDetailLabels(this.t),
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(mermaidRoot, buildGraphIdentityTitle(state.model));
this.appendRendererSelection(mermaidRoot, state.rendererSelection);
this.appendViewerToolbarControls(mermaidRoot);
shell3.topPane.appendChild(mermaidRoot);
return;
}
const contextRoot = renderObjectContext(state.context, {
onOpenObject: state.onOpenObject ?? void 0,
app: this.app,
interactionSourcePath: objectPath,
viewportState: this.objectGraphViewportState,
onViewportStateChange: this.createObjectViewportStateHandler(objectPath),
labels: getObjectContextLabels(this.t)
});
contextRoot.addClass("model-weave-object-context-no-margin");
const relatedList = Array.from(contextRoot.children).find(
(child) => child.instanceOf(HTMLElement) && (child.classList.contains("model-weave-object-context-list") || child.classList.contains("mdspec-related-list"))
);
if (relatedList) {
relatedList.remove();
shell3.bottomPane.appendChild(relatedList);
}
ensureGraphIdentityTitle(contextRoot, buildGraphIdentityTitle(state.model));
this.appendRendererSelection(contextRoot, state.rendererSelection);
this.appendViewerToolbarControls(contextRoot);
shell3.topPane.appendChild(contextRoot);
}
renderRelationsState(state) {
const model = state.model;
this.contentEl.createEl("h2", {
text: model.title ?? model.frontmatter.id?.toString() ?? "Relations"
});
if (model.relations.length === 0) {
this.contentEl.createEl("p", { text: "No relations defined." });
return;
}
const list = this.contentEl.createEl("ul");
for (const relation of model.relations) {
const label = relation.label ? ` (${relation.label})` : "";
list.createEl("li", {
text: `${relation.source} -[${relation.kind}]-> ${relation.target}${label}`
});
}
}
renderDomainsState(state) {
const shell3 = this.createViewerSplitShell(`domains:${state.model.path}`, 0.62);
shell3.bottomPane.addClass("model-weave-summary-details");
this.activeScrollContainer = shell3.bottomPane;
this.renderReviewSummaryPanel(shell3.bottomPane, {
model: state.model,
warnings: state.warnings,
sourceLinks: state.model.sourceLinks,
weaveMapAvailable: false
});
this.renderDomainMermaidDiagram(
shell3.topPane,
state.model.domains,
shell3.bottomPane,
state.colorScheme,
buildGraphIdentityTitle(state.model),
state.model.path
);
this.renderDomainTree(shell3.bottomPane, buildDomainTree(state.model.domains));
renderDiagnostics(
shell3.bottomPane,
state.warnings,
state.onOpenDiagnostic ?? void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
this.renderDomainRelationships(shell3.bottomPane, state.relationships);
this.renderSourceLinksSection(shell3.bottomPane, state.model.sourceLinks);
this.renderDomainDetails(shell3.bottomPane, state.model);
this.renderAppliedColorScheme(shell3.bottomPane, state.colorScheme, ["domain"]);
}
renderDomainDiagramState(state) {
const shell3 = this.createViewerSplitShell(
`domain-diagram:${state.resolved.diagram.path}`,
0.62
);
shell3.bottomPane.addClass("model-weave-summary-details");
this.activeScrollContainer = shell3.bottomPane;
this.renderReviewSummaryPanel(shell3.bottomPane, {
model: state.resolved.diagram,
warnings: state.warnings,
sourceLinks: state.resolved.diagram.sourceLinks,
weaveMapAvailable: false
});
this.renderDomainMermaidDiagram(
shell3.topPane,
state.resolved.domains,
shell3.bottomPane,
state.colorScheme,
buildGraphIdentityTitle(state.resolved.diagram),
state.resolved.diagram.path
);
this.renderDomainTree(shell3.bottomPane, buildDomainTree(state.resolved.domains));
renderDiagnostics(
shell3.bottomPane,
state.warnings,
state.onOpenDiagnostic ?? void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
this.renderDomainDiagramSourceSummary(
shell3.bottomPane,
state.resolved.sourceSummaries
);
this.renderDomainDiagramConflictSummary(
shell3.bottomPane,
state.resolved.conflicts
);
this.renderDomainRelationships(shell3.bottomPane, state.relationships);
this.renderSourceLinksSection(shell3.bottomPane, state.resolved.diagram.sourceLinks);
this.renderDomainDiagramDetails(shell3.bottomPane, state.resolved);
this.renderAppliedColorScheme(shell3.bottomPane, state.colorScheme, ["domain"]);
}
renderDomainDiagramSourceSummary(container, sources) {
const section = this.createCollapsibleSection(
container,
"domain-diagram:sources",
this.t("domainDiagram.preview.sources"),
true
);
if (sources.length === 0) {
section.createEl("p", {
text: this.t("domainDiagram.preview.noSources"),
cls: "model-weave-summary-muted"
});
return;
}
const list = section.createEl("div", { cls: "model-weave-summary-list" });
for (const source of sources) {
const card = list.createDiv({
cls: "model-weave-preview-section model-weave-summary-metadata"
});
card.createEl("h3", {
text: source.resolvedPath ?? source.ref.ref,
cls: "model-weave-preview-section-title"
});
this.renderDetailCard(card, [
{ label: this.t("domainDiagram.field.ref"), value: source.ref.ref },
{
label: this.t("domainDiagram.field.status"),
value: this.t(`domainDiagram.status.${source.status}`)
},
{
label: this.t("domains.preview.count"),
value: String(source.domainCount)
},
...source.ref.notes ? [{ label: this.t("domainDiagram.field.notes"), value: source.ref.notes }] : []
]);
}
}
renderDomainDiagramConflictSummary(container, conflicts) {
const section = this.createCollapsibleSection(
container,
"domain-diagram:conflicts",
this.t("domainDiagram.preview.conflicts"),
true
);
if (conflicts.length === 0) {
section.createEl("p", {
text: this.t("domainDiagram.preview.noConflicts"),
cls: "model-weave-summary-muted"
});
return;
}
const list = section.createEl("div", { cls: "model-weave-summary-list" });
for (const conflict of conflicts) {
const card = list.createDiv({
cls: "model-weave-preview-section model-weave-summary-metadata"
});
card.createEl("h3", {
text: `${this.t("domains.field.id")}: ${conflict.domainId}`,
cls: "model-weave-preview-section-title"
});
this.renderDetailCard(card, [
{
label: this.t("domainDiagram.field.conflict"),
value: conflict.field
},
{
label: this.t("domainDiagram.field.earlier"),
value: this.formatDomainConflictValue(conflict.earlierSourcePath, conflict.earlierValue)
},
{
label: this.t("domainDiagram.field.later"),
value: this.formatDomainConflictValue(conflict.laterSourcePath, conflict.laterValue)
},
{
label: this.t("domainDiagram.field.effective"),
value: conflict.effectiveSourcePath
}
]);
}
}
renderAppProcessDomainPlacementSummary(container, resolved) {
if (resolved.process.domains.length === 0 && resolved.sourceSummaries.length === 0 && resolved.placements.length === 0) {
return;
}
const section = this.createCollapsibleSection(
container,
"app-process:domain-placement",
this.t("appProcess.preview.domainSourcesPlacement"),
true
);
section.createEl("p", {
text: this.t("appProcess.preview.legacyLaneLayoutOnly"),
cls: "model-weave-summary-muted"
});
if (resolved.process.domains.length > 0) {
const localHeading = section.createEl("h3", {
text: this.t("appProcess.preview.localDomains"),
cls: "model-weave-preview-section-title"
});
localHeading.addClass("model-weave-summary-subtitle");
const localList = section.createEl("ul", { cls: "model-weave-summary-list" });
for (const domain of resolved.process.domains) {
const label = [
domain.name || domain.id,
domain.kind ? `[${domain.kind}]` : "",
domain.parent ? `parent: ${domain.parent}` : ""
].filter(Boolean).join(" ");
localList.createEl("li", { text: `${domain.id}: ${label}` });
}
}
if (resolved.sourceSummaries.length > 0) {
const sourcesHeading = section.createEl("h3", {
text: this.t("domainDiagram.preview.sources"),
cls: "model-weave-preview-section-title"
});
sourcesHeading.addClass("model-weave-summary-subtitle");
const sourceList = section.createEl("ul", { cls: "model-weave-summary-list" });
for (const source of resolved.sourceSummaries) {
const label = [
source.resolvedPath ?? source.ref.ref,
`status: ${source.status}`,
`domains: ${source.domainCount}`
].join(" / ");
sourceList.createEl("li", { text: label });
}
}
if (resolved.placements.length > 0) {
const placementsHeading = section.createEl("h3", {
text: this.t("appProcess.preview.domainPlacement"),
cls: "model-weave-preview-section-title"
});
placementsHeading.addClass("model-weave-summary-subtitle");
const placementList = section.createEl("ul", { cls: "model-weave-summary-list" });
for (const placement of resolved.placements) {
const domainLabel = placement.domain ? [
placement.domain.name || placement.domain.id,
placement.domain.kind ? `[${placement.domain.kind}]` : ""
].filter(Boolean).join(" ") : "unresolved";
const stepLabel = placement.stepLabel ? `${placement.stepLabel} [${placement.stepId}]` : placement.stepId;
placementList.createEl("li", {
text: `${stepLabel}: ${placement.domainId} (${domainLabel})`
});
}
}
}
renderDomainDiagramDetails(container, resolved) {
const details = this.createCollapsibleSection(
container,
"domain-diagram:details",
this.t("domains.preview.details"),
true
);
const diagram = resolved.diagram;
const overview = details.createDiv({
cls: "model-weave-preview-section model-weave-summary-metadata"
});
overview.createEl("h3", {
text: this.t("domains.preview.overview"),
cls: "model-weave-preview-section-title"
});
this.renderDetailCard(overview, [
{ label: this.t("domains.field.type"), value: "domain_diagram" },
{ label: this.t("domains.field.id"), value: diagram.id || this.t("domains.value.none") },
{ label: this.t("domains.field.name"), value: diagram.name || this.t("domains.value.none") },
{
label: this.t("domainDiagram.preview.sourceCount"),
value: String(resolved.sourceSummaries.length)
},
{ label: this.t("domains.preview.count"), value: String(resolved.domains.length) },
{ label: this.t("domains.field.path"), value: diagram.path }
]);
this.renderDomainTable(details, resolved.domains);
}
formatDomainConflictValue(sourcePath, value) {
const displayValue = value?.trim() || this.t("domains.value.none");
return `${sourcePath}: ${displayValue}`;
}
renderDomainRelationships(container, relationships) {
if (relationships.length === 0) {
return;
}
const section = this.createCollapsibleSection(
container,
"domains:relationships",
this.t("domains.preview.relationships"),
true
);
const list = section.createEl("div", { cls: "model-weave-summary-list" });
for (const relationship of relationships) {
const card = list.createDiv({
cls: "model-weave-preview-section model-weave-summary-metadata"
});
card.createEl("h3", {
text: `${this.t("domains.field.id")}: ${relationship.domain.id}`,
cls: "model-weave-preview-section-title"
});
this.renderDetailCard(card, [
{
label: this.t("domains.field.name"),
value: relationship.domain.name || relationship.domain.id
},
{
label: this.t("domains.field.kind"),
value: relationship.domain.kind || this.t("domains.value.none")
},
{
label: this.t("domains.relationship.parent"),
value: relationship.parentId || this.t("domains.relationship.none")
},
{
label: this.t("domains.relationship.children"),
value: this.formatDomainRelationshipValues(relationship.childIds)
}
]);
if (relationship.domain.description) {
this.renderDomainRelationshipList(
card,
this.t("domains.field.description"),
[relationship.domain.description]
);
}
this.renderDomainRelationshipList(
card,
this.t("domains.relationship.definedIn"),
relationship.definedIn.map((entry) => entry.path)
);
this.renderDomainRelationshipList(
card,
this.t("domains.relationship.conflicts"),
relationship.conflicts.map(
(field) => this.t("domains.relationship.conflictField", { field })
)
);
this.renderDomainRelationshipList(
card,
this.t("domains.relationship.dfdLocalDomains"),
relationship.dfdLocalDomainReferences.map((entry) => entry.path)
);
this.renderDomainRelationshipList(
card,
this.t("domains.relationship.dfdObjects"),
relationship.dfdObjectReferences.map(
(entry) => entry.label ? `${entry.path} / ${entry.objectId}: ${entry.label}` : `${entry.path} / ${entry.objectId}`
)
);
}
}
renderDomainRelationshipList(container, label, values) {
const block = container.createDiv({ cls: "model-weave-summary-metadata" });
block.createEl("h4", {
text: label,
cls: "model-weave-preview-section-title"
});
if (values.length === 0) {
block.createEl("p", {
text: this.t("domains.relationship.none"),
cls: "model-weave-summary-muted"
});
return;
}
const list = block.createEl("ul", { cls: "model-weave-summary-list" });
for (const value of values) {
list.createEl("li", { text: value });
}
}
formatDomainRelationshipValues(values) {
return values.length > 0 ? values.join(", ") : this.t("domains.relationship.none");
}
renderDomainDetails(container, model) {
const details = this.createCollapsibleSection(
container,
"domains:details",
this.t("domains.preview.details"),
true
);
const overview = details.createDiv({
cls: "model-weave-preview-section model-weave-summary-metadata"
});
overview.createEl("h3", {
text: this.t("domains.preview.overview"),
cls: "model-weave-preview-section-title"
});
this.renderDetailCard(overview, [
{ label: this.t("domains.field.type"), value: "domains" },
{ label: this.t("domains.field.id"), value: model.id || this.t("domains.value.none") },
{ label: this.t("domains.field.name"), value: model.name || this.t("domains.value.none") },
{ label: this.t("domains.preview.count"), value: String(model.domains.length) },
{ label: this.t("domains.field.path"), value: model.path }
]);
this.renderDomainTable(details, model.domains);
}
renderColorSchemeState(state) {
this.contentEl.createEl("h2", {
text: state.model.title ?? state.model.name
});
renderDiagnostics(
this.contentEl,
state.warnings,
state.onOpenDiagnostic ?? void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
const section = this.createCollapsibleSection(
this.contentEl,
"color-scheme:colors",
this.t("colorScheme.preview.colors"),
true
);
const canPickColors = !state.warnings.some(
(warning) => warning.code === "invalid-table-column" && (warning.field === "Colors" || warning.context?.section === "Colors" || /section "Colors"/.test(warning.message))
);
this.renderColorSchemeTable(section, state.model.colors, canPickColors);
}
renderColorSchemeTable(container, colors, canPickColors) {
const tableWrap = container.createDiv({ cls: "model-weave-table-wrap" });
const table = tableWrap.createEl("table", {
cls: "model-weave-summary-table model-weave-data-table"
});
const headerRow = table.createEl("thead").createEl("tr");
for (const key of [
"colorScheme.field.target",
"colorScheme.field.kind",
"colorScheme.field.fill",
"colorScheme.field.stroke",
"colorScheme.field.text",
"colorScheme.preview.swatch",
"colorScheme.field.notes"
]) {
headerRow.createEl("th", {
text: this.t(key),
cls: "model-weave-summary-th"
});
}
const tbody = table.createEl("tbody");
if (colors.length === 0) {
const row = tbody.createEl("tr");
row.createEl("td", {
text: this.t("colorScheme.preview.empty"),
attr: { colspan: "7" },
cls: "model-weave-summary-muted"
});
return;
}
for (const color of colors) {
this.renderColorSchemeTableRow(tbody, color, canPickColors);
}
}
renderAppliedColorScheme(container, colorScheme, targets) {
if (!colorScheme) {
return;
}
const normalizedTargets = targets.map((target) => target.trim()).filter(Boolean);
if (normalizedTargets.length === 0) {
return;
}
const section = this.createCollapsibleSection(
container,
`color-scheme:applied:${normalizedTargets.join(":")}`,
this.t("colorScheme.preview.applied"),
false
);
renderAppliedColorSchemeSectionContent(
section,
colorScheme,
getAppliedColorSchemeRowsForTargets(colorScheme, normalizedTargets),
normalizedTargets,
this.t
);
}
renderColorSchemeTableRow(tbody, color, canPickColors) {
const row = tbody.createEl("tr");
row.createEl("td", { text: color.target ?? this.t("domains.value.none") });
row.createEl("td", { text: color.kind });
this.renderColorSchemeColorCell(row, color, "fill", canPickColors);
this.renderColorSchemeColorCell(row, color, "stroke", canPickColors);
this.renderColorSchemeColorCell(row, color, "text", canPickColors);
const swatchCell = row.createEl("td");
const swatch = swatchCell.createSpan({
cls: "model-weave-color-swatch"
});
if (color.fill) {
swatch.style.backgroundColor = color.fill;
}
if (color.stroke) {
swatch.style.borderColor = color.stroke;
}
if (color.text) {
swatch.style.color = color.text;
}
swatch.textContent = "Aa";
row.createEl("td", { text: color.notes ?? "" });
}
renderColorSchemeColorCell(row, color, columnName, canPickColors) {
const value = color[columnName] ?? "";
const normalized = normalizeHexColorForPicker(value);
const cell = row.createEl("td");
const control = cell.createDiv({ cls: "model-weave-color-cell-control" });
const swatch = control.createSpan({
cls: normalized ? "model-weave-color-cell-swatch" : "model-weave-color-cell-swatch model-weave-color-cell-swatch-empty",
attr: { "aria-hidden": "true" }
});
if (normalized) {
swatch.style.backgroundColor = normalized;
}
const text = control.createSpan({
text: value || this.t("colorScheme.preview.blankColor"),
cls: value ? "model-weave-color-cell-value" : "model-weave-color-cell-value model-weave-summary-empty-cell"
});
if (normalized) {
text.title = normalized;
} else if (value) {
text.title = this.t("colorScheme.preview.unsupportedColor");
}
if (!canPickColors) {
return;
}
const input = control.createEl("input", {
cls: "model-weave-color-picker-input",
attr: {
type: "color",
value: normalized ?? "#ffffff",
"aria-label": this.t("colorScheme.preview.pickAria", {
column: this.t(`colorScheme.field.${columnName}`)
})
}
});
const button = control.createEl("button", {
text: this.t("colorScheme.preview.pick"),
cls: "model-weave-color-picker-button",
attr: { type: "button" }
});
button.addEventListener("click", () => {
input.click();
});
input.addEventListener("change", () => {
void this.applyColorSchemeColorPick(color.rowIndex, columnName, input.value);
});
}
async applyColorSchemeColorPick(rowIndex, columnName, value) {
try {
const file = this.getCurrentTFileForQuickFix();
if (!file) {
new import_obsidian7.Notice(this.t("colorScheme.preview.pickFailed"));
return;
}
const markdown = await this.app.vault.read(file);
const result = updateColorSchemeColorCell(markdown, {
rowIndex,
columnName,
value
});
if (!result.changed) {
const noticeKey = result.status === "unchanged" ? "colorScheme.preview.pickUnchanged" : "colorScheme.preview.pickFailed";
new import_obsidian7.Notice(this.t(noticeKey));
return;
}
await this.app.vault.modify(file, result.updatedMarkdown);
new import_obsidian7.Notice(this.t("colorScheme.preview.pickApplied"));
this.renderCurrentState();
} catch {
new import_obsidian7.Notice(this.t("colorScheme.preview.pickFailed"));
}
}
renderDomainTable(container, domains) {
const section = this.createCollapsibleSection(
container,
"domains:list",
this.t("domains.preview.list"),
true
);
section.addClass("model-weave-table-wrap");
const table = section.createEl("table", {
cls: "model-weave-summary-table model-weave-data-table"
});
const headerRow = table.createEl("thead").createEl("tr");
for (const key of [
"domains.field.id",
"domains.field.name",
"domains.field.kind",
"domains.field.parent",
"domains.field.description"
]) {
headerRow.createEl("th", {
text: this.t(key),
cls: "model-weave-summary-th"
});
}
const tbody = table.createEl("tbody");
if (domains.length === 0) {
const row = tbody.createEl("tr");
row.createEl("td", {
text: this.t("domains.preview.empty"),
cls: "model-weave-summary-td model-weave-summary-empty-cell",
attr: { colspan: "5" }
});
return;
}
for (const domain of domains) {
const row = tbody.createEl("tr");
for (const value of [
domain.id,
domain.name || domain.id,
domain.kind,
domain.parent,
domain.description
]) {
row.createEl("td", {
text: value || this.t("domains.value.none"),
cls: "model-weave-summary-td"
});
}
}
}
renderDomainTree(container, roots) {
const section = this.createCollapsibleSection(
container,
"domains:tree",
this.t("domains.preview.tree"),
true
);
if (roots.length === 0) {
section.createEl("p", {
text: this.t("domains.preview.empty"),
cls: "model-weave-summary-muted"
});
return;
}
const list = section.createEl("ul", { cls: "model-weave-summary-list" });
for (const root of roots) {
this.renderDomainTreeNode(list, root, /* @__PURE__ */ new Set());
}
}
renderDomainTreeNode(list, node, visited) {
const item = list.createEl("li", {
text: this.getDomainLabel(node.domain)
});
if (visited.has(node.domain.id) || node.children.length === 0) {
return;
}
const nextVisited = new Set(visited);
nextVisited.add(node.domain.id);
const childList = item.createEl("ul", { cls: "model-weave-summary-list" });
for (const child of node.children) {
this.renderDomainTreeNode(childList, child, nextVisited);
}
}
getDomainLabel(domain) {
const displayName = domain.name || domain.id;
return domain.kind ? `${displayName} (${domain.kind})` : displayName;
}
getDiagnosticLanguage() {
return this.viewerPreferences.uiLanguage === "auto" ? void 0 : this.viewerPreferences.uiLanguage;
}
renderDomainMermaidDiagram(container, domains, sourcePanelContainer, colorScheme, graphTitle, interactionSourcePath) {
if (domains.length === 0) {
const section = this.createCollapsibleSection(
container,
"domains:diagram",
this.t("domains.preview.diagram"),
true
);
section.createEl("p", {
text: this.t("domains.preview.diagramEmpty"),
cls: "model-weave-summary-muted"
});
return;
}
const diagramRoot = renderDomainsMermaidDiagram(domains, {
title: graphTitle ?? this.getDomainDiagramModeLabel(this.domainsDiagramMode),
mode: this.domainsDiagramMode,
renderFailedMessage: this.t("domains.preview.diagramRenderFailed"),
fitVerticalAlign: "top",
sourcePanelContainer,
sourcePanelPlacement: sourcePanelContainer ? "prepend" : void 0,
...getMermaidSourceLabels(this.t),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportCurrentDiagramAsPngWithNotice(),
onExportAndOpenPng: () => this.exportCurrentDiagramAsPngAndOpenWithNotice(),
viewportState: this.domainsMermaidViewportState,
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug,
colorScheme,
app: this.app,
interactionSourcePath
});
ensureGraphIdentityTitle(diagramRoot, graphTitle ?? this.getDomainDiagramModeLabel(this.domainsDiagramMode));
this.appendDomainDiagramModeSelector(diagramRoot);
this.appendViewerToolbarControls(diagramRoot);
container.appendChild(diagramRoot);
}
appendDomainDiagramModeSelector(container) {
const toolbar = container.querySelector(".mdspec-zoom-toolbar");
if (!toolbar) {
return;
}
toolbar.addClass("model-weave-render-mode-toolbar-host");
toolbar.querySelector(".model-weave-domain-mode-select-group")?.remove();
const doc = container.ownerDocument;
const wrapper = doc.createElement("div");
wrapper.className = "model-weave-domain-mode-select-group model-weave-render-mode-row";
const label = doc.createElement("span");
label.addClass("model-weave-render-mode-label");
label.textContent = this.t("domains.preview.viewMode");
wrapper.appendChild(label);
const select = doc.createElement("select");
select.addClass("model-weave-domain-mode-select");
for (const mode of ["mindmap", "area", "tree"]) {
const option = doc.createElement("option");
option.value = mode;
option.textContent = this.getDomainDiagramModeLabel(mode);
option.selected = this.domainsDiagramMode === mode;
select.appendChild(option);
}
select.addEventListener("change", () => {
const nextMode = select.value;
if (this.domainsDiagramMode === nextMode) {
return;
}
const shouldRestoreViewOnly = this.viewOnlyEnabled && this.viewOnlyTarget?.classList.contains("model-weave-domains-mermaid");
this.domainsDiagramMode = nextMode;
if (this.domainsMermaidViewportState.viewMode === "fit") {
resetGraphViewportState(this.domainsMermaidViewportState);
}
this.renderCurrentState();
this.restoreCurrentScrollPosition();
if (shouldRestoreViewOnly) {
const view = this.contentEl.ownerDocument.defaultView;
view?.requestAnimationFrame(() => {
view.requestAnimationFrame(() => {
const nextTarget = this.contentEl.querySelector(
".model-weave-domains-mermaid"
);
if (nextTarget) {
this.setViewOnlyMode(true, { target: nextTarget });
}
});
});
}
});
wrapper.appendChild(select);
const rightGroup = toolbar.querySelector(".model-weave-zoom-toolbar-right") ?? toolbar;
rightGroup.appendChild(wrapper);
}
appendAppProcessBusinessFlowDirectionSelector(container, filePath) {
const toolbar = container.querySelector(".mdspec-zoom-toolbar");
if (!toolbar) {
return;
}
toolbar.addClass("model-weave-render-mode-toolbar-host");
toolbar.querySelector(".model-weave-business-flow-direction-select-group")?.remove();
const doc = container.ownerDocument;
const wrapper = doc.createElement("div");
wrapper.className = "model-weave-business-flow-direction-select-group model-weave-render-mode-row";
const label = doc.createElement("span");
label.addClass("model-weave-render-mode-label");
label.textContent = this.t("appProcess.businessFlow.direction");
wrapper.appendChild(label);
const select = doc.createElement("select");
select.addClass("model-weave-business-flow-direction-select");
for (const direction of ["LR", "TD"]) {
const option = doc.createElement("option");
option.value = direction;
option.textContent = this.getAppProcessBusinessFlowDirectionLabel(direction);
option.selected = this.getAppProcessBusinessFlowDirectionForFile(filePath) === direction;
select.appendChild(option);
}
select.addEventListener("change", () => {
this.setAppProcessBusinessFlowDirection(select.value, filePath);
});
wrapper.appendChild(select);
const rightGroup = toolbar.querySelector(".model-weave-zoom-toolbar-right") ?? toolbar;
rightGroup.appendChild(wrapper);
}
appendFlowDiagramViewSelector(container, filePath) {
const toolbar = container.querySelector(".mdspec-zoom-toolbar");
if (!toolbar) {
return;
}
toolbar.addClass("model-weave-render-mode-toolbar-host");
toolbar.querySelector(".model-weave-flow-diagram-view-select-group")?.remove();
const wrapper = container.ownerDocument.createElement("div");
wrapper.className = "model-weave-flow-diagram-view-select-group model-weave-render-mode-row";
const label = container.ownerDocument.createElement("span");
label.addClass("model-weave-render-mode-label");
label.textContent = this.t("flowDiagram.viewMode");
wrapper.appendChild(label);
const select = container.ownerDocument.createElement("select");
select.addClass("model-weave-flow-diagram-view-select");
for (const mode of ["detail", "screen"]) {
const option = container.ownerDocument.createElement("option");
option.value = mode;
option.textContent = this.t(`flowDiagram.viewMode.${mode}`);
option.selected = this.getFlowDiagramViewModeForFile(filePath) === mode;
select.appendChild(option);
}
select.addEventListener("change", () => {
this.setFlowDiagramViewMode(select.value, filePath);
});
wrapper.appendChild(select);
const rightGroup = toolbar.querySelector(".model-weave-zoom-toolbar-right") ?? toolbar;
rightGroup.appendChild(wrapper);
}
appendAppProcessFlowConnectControl(container, filePath) {
const toolbar = container.querySelector(".mdspec-zoom-toolbar");
if (!toolbar) {
return;
}
toolbar.addClass("model-weave-render-mode-toolbar-host");
toolbar.querySelector(".model-weave-business-flow-connect-button")?.remove();
const button = container.ownerDocument.createElement("button");
button.type = "button";
button.addClass("model-weave-zoom-toolbar-button");
button.addClass("model-weave-business-flow-connect-button");
this.updateAppProcessFlowConnectButton(button, filePath);
button.addEventListener("click", (event) => {
event.preventDefault();
this.setAppProcessFlowConnectMode(
!this.isAppProcessFlowConnectModeEnabled(filePath),
filePath
);
});
const status = this.createAppProcessFlowConnectStatus(container, filePath);
const controls = toolbar.querySelector(".model-weave-zoom-toolbar-controls");
if (controls) {
const exportButton = controls.querySelector(
".model-weave-zoom-toolbar-export-png"
);
controls.insertBefore(button, exportButton ?? null);
if (status) {
controls.insertBefore(status, exportButton ?? null);
}
return;
}
const rightGroup = toolbar.querySelector(".model-weave-zoom-toolbar-right") ?? toolbar;
rightGroup.appendChild(button);
if (status) {
rightGroup.appendChild(status);
}
}
createAppProcessFlowConnectStatus(container, filePath) {
if (!this.isAppProcessFlowConnectModeEnabled(filePath)) {
return null;
}
const status = container.ownerDocument.createElement("span");
status.addClass("model-weave-business-flow-connect-status");
const selected = this.appProcessFlowConnectSourceStepId;
status.textContent = selected ? this.t("appProcess.businessFlow.connectFlow.statusSelected", { stepId: selected }) : this.t("appProcess.businessFlow.connectFlow.statusReady");
return status;
}
updateAppProcessFlowConnectButton(button, filePath) {
const enabled = this.isAppProcessFlowConnectModeEnabled(filePath);
const selected = enabled ? this.appProcessFlowConnectSourceStepId : null;
button.textContent = this.t("appProcess.businessFlow.connectFlow.short");
button.title = selected ? this.t("appProcess.businessFlow.connectFlow.selectedTitle", { stepId: selected }) : this.t("appProcess.businessFlow.connectFlow.title");
button.setAttribute("aria-label", button.title);
button.setAttribute("aria-pressed", enabled ? "true" : "false");
button.toggleClass("is-active", enabled);
}
isAppProcessFlowConnectModeEnabled(filePath) {
return this.appProcessFlowConnectFilePath === filePath && this.appProcessFlowConnectModeEnabled;
}
setAppProcessFlowConnectMode(enabled, filePath) {
this.appProcessFlowConnectFilePath = filePath;
this.appProcessFlowConnectModeEnabled = enabled;
this.appProcessFlowConnectSourceStepId = null;
new import_obsidian7.Notice(
enabled ? this.t("appProcess.businessFlow.connectFlow.enabled") : this.t("appProcess.businessFlow.connectFlow.disabled")
);
this.renderCurrentState();
}
async handleAppProcessFlowConnectStepClick(filePath, stepId) {
if (!this.isAppProcessFlowConnectModeEnabled(filePath)) {
return;
}
const from = this.appProcessFlowConnectSourceStepId;
if (!from) {
this.appProcessFlowConnectSourceStepId = stepId;
new import_obsidian7.Notice(this.t("appProcess.businessFlow.connectFlow.selected", { stepId }));
this.renderCurrentState();
return;
}
if (from === stepId) {
this.appProcessFlowConnectSourceStepId = null;
new import_obsidian7.Notice(this.t("appProcess.businessFlow.connectFlow.cleared"));
this.renderCurrentState();
return;
}
const file = this.app.vault.getAbstractFileByPath(filePath);
if (!(file instanceof import_obsidian7.TFile)) {
this.appProcessFlowConnectSourceStepId = null;
new import_obsidian7.Notice(this.t("appProcess.businessFlow.connectFlow.failed"));
this.renderCurrentState();
return;
}
const markdown = await this.app.vault.read(file);
const result = addAppProcessFlow(markdown, { from, to: stepId });
this.appProcessFlowConnectSourceStepId = null;
if (!result.changed) {
const key = result.status === "duplicate" ? "appProcess.businessFlow.connectFlow.duplicate" : "appProcess.businessFlow.connectFlow.failed";
new import_obsidian7.Notice(this.t(key, { from, to: stepId }));
this.renderCurrentState();
return;
}
await this.app.vault.modify(file, result.updatedMarkdown);
new import_obsidian7.Notice(this.t("appProcess.businessFlow.connectFlow.added", { from, to: stepId }));
}
getAppProcessBusinessFlowDirectionLabel(direction) {
return direction === "TD" ? this.t("appProcess.businessFlow.direction.td") : this.t("appProcess.businessFlow.direction.lr");
}
getAppProcessBusinessFlowDirection(state) {
return resolveAppProcessBusinessFlowDirection({
toolbarOverride: this.getAppProcessBusinessFlowDirectionOverride(state.filePath),
frontmatterDirection: state.businessFlowDirection,
settingsDefaultDirection: this.viewerPreferences.defaultBusinessFlowDirection
});
}
getAppProcessBusinessFlowDirectionForFile(filePath) {
const summaryState = this.state.mode === "summary" && this.state.filePath === filePath ? this.state : null;
return resolveAppProcessBusinessFlowDirection({
toolbarOverride: this.getAppProcessBusinessFlowDirectionOverride(filePath),
frontmatterDirection: summaryState?.businessFlowDirection,
settingsDefaultDirection: this.viewerPreferences.defaultBusinessFlowDirection
});
}
getAppProcessBusinessFlowDirectionOverride(filePath) {
return this.appProcessBusinessFlowDirectionFilePath === filePath ? this.appProcessBusinessFlowDirectionOverride : null;
}
getFlowDiagramViewMode(state) {
const diagram = state.diagram.diagram;
if (diagram.schema !== "flow_diagram") {
return "detail";
}
return this.flowDiagramViewModes.getOrInitialize(
diagram.path,
diagram,
this.viewerPreferences.defaultFlowDiagramViewMode
);
}
getFlowDiagramViewModeForFile(filePath) {
const diagramState = this.state.mode === "diagram" && this.state.diagram.diagram.path === filePath ? this.state : null;
if (!diagramState || diagramState.diagram.diagram.schema !== "flow_diagram") {
return "detail";
}
return this.flowDiagramViewModes.getOrInitialize(
filePath,
diagramState.diagram.diagram,
this.viewerPreferences.defaultFlowDiagramViewMode
);
}
setFlowDiagramViewMode(mode, filePath) {
if (mode !== "detail" && mode !== "screen") {
return;
}
if (this.getFlowDiagramViewModeForFile(filePath) === mode) {
return;
}
this.flowDiagramViewModes.set(filePath, mode);
this.viewportStateCache.delete(filePath);
resetGraphViewportState(this.diagramViewportState);
this.renderCurrentState();
this.restoreCurrentScrollPosition();
}
setAppProcessBusinessFlowDirection(direction, filePath) {
const nextDirection = normalizeAppProcessBusinessFlowDirection(direction);
if (!nextDirection) {
return;
}
if (this.appProcessBusinessFlowDirectionOverride === nextDirection && this.appProcessBusinessFlowDirectionFilePath === filePath) {
return;
}
const shouldRestoreViewOnly = this.viewOnlyEnabled && this.viewOnlyTarget?.classList.contains("model-weave-app-process-business-flow");
this.appProcessBusinessFlowDirectionOverride = nextDirection;
this.appProcessBusinessFlowDirectionFilePath = filePath;
this.viewportStateCache.delete(filePath);
resetGraphViewportState(this.screenPreviewViewportState);
this.renderCurrentState();
this.restoreCurrentScrollPosition();
if (shouldRestoreViewOnly) {
const view = this.contentEl.ownerDocument.defaultView;
view?.requestAnimationFrame(() => {
view.requestAnimationFrame(() => {
const nextTarget = this.contentEl.querySelector(
".model-weave-app-process-business-flow"
);
if (nextTarget) {
this.setViewOnlyMode(true, { target: nextTarget });
}
});
});
}
}
getDomainDiagramModeLabel(mode) {
if (mode === "mindmap") {
return this.t("domains.preview.mindmap");
}
if (mode === "tree") {
return this.t("domains.preview.treeMode");
}
return this.t("domains.preview.area");
}
renderSummaryState(state) {
const hasScreenPreview = (state.layoutBlocks?.length ?? 0) > 0;
const hasBusinessFlow = (state.businessFlow?.steps.length ?? 0) > 0;
if (hasScreenPreview || hasBusinessFlow) {
const shell3 = this.createViewerSplitShell(`summary:${state.filePath}`, 0.48);
this.activeScrollContainer = shell3.bottomPane;
if (hasScreenPreview) {
const screenRoot = createScreenPreviewDiagram(
buildScreenPreviewData(state, this.t),
{
app: this.app,
viewportState: this.screenPreviewViewportState,
onViewportStateChange: this.createScreenPreviewViewportStateHandler(
state.filePath
),
onNavigateToLocation: state.onNavigateToLocation,
onOpenLinkedFile: state.onOpenLinkedFile
}
);
ensureGraphIdentityTitle(screenRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(screenRoot);
shell3.topPane.appendChild(screenRoot);
} else if (state.businessFlow) {
if (this.screenPreviewViewportState.viewMode === "fit") {
resetGraphViewportState(this.screenPreviewViewportState);
}
const businessFlowRoot = renderAppProcessBusinessFlow(state.businessFlow, {
sourcePanelContainer: shell3.bottomPane,
sourcePanelPlacement: "prepend",
...getMermaidSourceLabels(this.t),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportCurrentDiagramAsPngWithNotice(),
onExportAndOpenPng: () => this.exportCurrentDiagramAsPngAndOpenWithNotice(),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug,
colorScheme: state.colorScheme,
flowDirection: this.getAppProcessBusinessFlowDirection(state),
app: this.app,
interactionSourcePath: state.filePath,
interactionIndex: state.interactionIndex,
onStepNodeClick: this.isAppProcessFlowConnectModeEnabled(state.filePath) ? (stepId) => this.handleAppProcessFlowConnectStepClick(state.filePath, stepId) : void 0,
viewportState: this.screenPreviewViewportState,
onViewportStateChange: this.createScreenPreviewViewportStateHandler(
state.filePath
)
});
ensureGraphIdentityTitle(businessFlowRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(businessFlowRoot);
this.appendAppProcessFlowConnectControl(businessFlowRoot, state.filePath);
this.appendAppProcessBusinessFlowDirectionSelector(businessFlowRoot, state.filePath);
shell3.topPane.appendChild(businessFlowRoot);
this.renderSummaryDetails(shell3.bottomPane, state, {
suppressBusinessFlowChart: true
});
return;
}
this.renderSummaryDetails(shell3.bottomPane, state, {
suppressBusinessFlowChart: hasBusinessFlow
});
return;
}
const wrapper = this.contentEl.createDiv();
wrapper.addClass("model-weave-summary-section");
wrapper.addClass("model-weave-summary-details");
this.activeScrollContainer = wrapper;
this.renderSummaryDetails(wrapper, state);
}
renderSummaryDetails(container, state, options = {}) {
container.addClass("model-weave-summary-details");
if (state.summaryKind === "screen") {
this.renderScreenSummaryDetails(container, state);
return;
}
container.createEl("h2", { text: state.title });
this.renderReviewSummaryPanel(container, {
model: {
title: state.title,
fileType: state.businessFlow ? "app_process" : state.summaryKind,
id: findSummaryMetadataValue(state, ["id", "model id", "model_id"])
},
warnings: state.warnings,
impactSummary: state.impactSummary,
sourceLinks: state.sourceLinks,
weaveMapAvailable: Boolean(state.weaveMapMermaidSource)
});
if (state.message) {
container.createEl("p", {
text: state.message,
cls: "model-weave-summary-muted"
});
}
renderDiagnostics(
container,
state.warnings,
void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
if (state.metadata.length > 0) {
const metadata = container.createDiv({
cls: "model-weave-preview-section model-weave-summary-metadata"
});
metadata.createEl("h3", {
text: "Overview",
cls: "model-weave-preview-section-title"
});
this.renderDetailCard(metadata, state.metadata);
}
this.renderImpactSummarySection(
container,
state.impactSummary,
state.onCopyImpactSummary,
state.onOpenImpactModel,
state.weaveMapMermaidSource,
state.colorScheme
);
this.renderSourceLinksSection(container, state.sourceLinks);
if (state.appProcessDomainPlacement) {
this.renderAppProcessDomainPlacementSummary(
container,
state.appProcessDomainPlacement
);
}
if (state.counts.length > 0) {
const counts = container.createDiv({
cls: "model-weave-preview-section model-weave-summary-counts"
});
counts.createEl("h3", {
text: this.t("summary.counts"),
cls: "model-weave-preview-section-title"
});
const list = counts.createEl("ul", { cls: "model-weave-summary-list" });
for (const entry of state.counts) {
list.createEl("li", {
text: `${this.localizeSummaryCountLabel(entry.label)}: ${entry.value}`
});
}
}
if (!options.suppressBusinessFlowChart && state.businessFlow && state.businessFlow.steps.length > 0) {
const section = container.createDiv({
cls: "model-weave-preview-section model-weave-app-process-business-flow-section"
});
if (this.screenPreviewViewportState.viewMode === "fit") {
resetGraphViewportState(this.screenPreviewViewportState);
}
const businessFlowRoot = renderAppProcessBusinessFlow(state.businessFlow, {
viewportState: this.screenPreviewViewportState,
colorScheme: state.colorScheme,
flowDirection: this.getAppProcessBusinessFlowDirection(state),
app: this.app,
interactionSourcePath: state.filePath,
interactionIndex: state.interactionIndex,
onStepNodeClick: this.isAppProcessFlowConnectModeEnabled(state.filePath) ? (stepId) => this.handleAppProcessFlowConnectStepClick(state.filePath, stepId) : void 0,
...getGraphExportLabels(this.t),
onExportPng: () => this.exportCurrentDiagramAsPngWithNotice(),
onExportAndOpenPng: () => this.exportCurrentDiagramAsPngAndOpenWithNotice(),
onViewportStateChange: this.createScreenPreviewViewportStateHandler(
state.filePath
)
});
ensureGraphIdentityTitle(businessFlowRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(businessFlowRoot);
this.appendAppProcessFlowConnectControl(businessFlowRoot, state.filePath);
this.appendAppProcessBusinessFlowDirectionSelector(businessFlowRoot, state.filePath);
section.appendChild(businessFlowRoot);
}
if (state.sections.length > 0) {
const sections = this.createCollapsibleSection(
container,
"detectedSections",
this.t("summary.detectedSections"),
true
);
const list = sections.createEl("ul", { cls: "model-weave-summary-list" });
for (const section of state.sections) {
const item = list.createEl("li", { text: section.label });
this.bindLocationNavigation(item, state.onNavigateToLocation, section);
}
}
for (const textSection of state.textSections ?? []) {
if (textSection.lines.length === 0) {
continue;
}
const section = this.createCollapsibleSection(
container,
`text:${textSection.title}`,
this.localizeSummarySectionTitle(textSection.title),
true
);
const markdown = textSection.lines.join("\n").trim();
if (!markdown) {
continue;
}
const markdownContainer = section.createDiv({
cls: "model-weave-summary-markdown"
});
void import_obsidian7.MarkdownRenderer.render(
this.app,
markdown,
markdownContainer,
state.filePath,
this
);
}
for (const table of state.tables ?? []) {
this.renderSummaryTable(container, state, table, true);
}
if ((state.localProcesses?.length ?? 0) > 0) {
const localProcesses = this.createCollapsibleSection(
container,
"localProcesses",
"Local Processes",
true
);
const list = localProcesses.createEl("ul", { cls: "model-weave-summary-list" });
for (const process of state.localProcesses ?? []) {
const item = list.createEl("li", { text: process.label });
this.bindLocationNavigation(item, state.onNavigateToLocation, process);
}
}
if ((state.navigationLists?.length ?? 0) > 0) {
for (const navigationList of state.navigationLists ?? []) {
const section = this.createCollapsibleSection(
container,
`navigation:${navigationList.title}`,
navigationList.title,
true
);
const list = section.createEl("ul", { cls: "model-weave-summary-list" });
for (const itemInfo of navigationList.items) {
const item = list.createEl("li", { text: itemInfo.label });
this.bindLocationNavigation(item, state.onNavigateToLocation, itemInfo);
}
}
}
if ((state.relatedReferences?.length ?? 0) > 0) {
const related = this.createCollapsibleSection(
container,
"relatedReferences",
"Related References",
true
);
const list = related.createEl("ul", { cls: "model-weave-summary-list" });
for (const reference of state.relatedReferences ?? []) {
const label = typeof reference.count === "number" && reference.count > 1 ? `${reference.label} \u2014 ${reference.count} occurrences` : reference.label;
const item = list.createEl("li", { text: label });
this.bindLocationNavigation(item, state.onNavigateToLocation, reference);
}
}
if (state.businessFlow && state.businessFlow.steps.length > 0) {
this.renderAppliedColorScheme(
container,
state.colorScheme,
this.getImpactColorSchemeTargets(
getAppProcessBusinessFlowColorSchemeTargets(state.businessFlow),
state.impactSummary
)
);
}
}
renderScreenSummaryDetails(container, state) {
container.createEl("h2", { text: state.title });
this.renderReviewSummaryPanel(container, {
model: {
title: state.title,
fileType: "screen",
id: findSummaryMetadataValue(state, ["id", "model id", "model_id"])
},
warnings: state.warnings,
impactSummary: state.impactSummary,
sourceLinks: state.sourceLinks,
weaveMapAvailable: Boolean(state.weaveMapMermaidSource)
});
renderDiagnostics(
container,
state.warnings,
void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
if (state.metadata.length > 0) {
const overview = container.createDiv({
cls: "model-weave-preview-section model-weave-screen-preview-section-overview"
});
overview.createEl("h3", {
text: "Screen overview",
cls: "model-weave-preview-section-title"
});
this.renderDetailCard(overview, state.metadata);
}
this.renderImpactSummarySection(
container,
state.impactSummary,
state.onCopyImpactSummary,
state.onOpenImpactModel,
state.weaveMapMermaidSource,
state.colorScheme
);
this.renderSourceLinksSection(container, state.sourceLinks);
if (state.counts.length > 0) {
const counts = container.createDiv({
cls: "model-weave-preview-section model-weave-screen-preview-section-counts"
});
counts.createEl("h3", {
text: this.t("summary.counts"),
cls: "model-weave-preview-section-title"
});
const list = counts.createEl("ul", { cls: "model-weave-summary-list" });
for (const entry of state.counts) {
list.createEl("li", {
text: `${this.localizeSummaryCountLabel(entry.label)}: ${entry.value}`
});
}
}
const tablesByTitle = new Map((state.tables ?? []).map((table) => [table.title, table]));
this.renderSummaryTable(container, state, tablesByTitle.get("Structure / Layout"), true);
this.renderSummaryTable(container, state, tablesByTitle.get("UI Elements / Fields"), true);
this.renderSummaryTable(container, state, tablesByTitle.get("Behavior / Actions"), true);
if ((state.localProcesses?.length ?? 0) > 0) {
this.renderSummaryNavigationList(
container,
state,
"Local processes",
state.localProcesses ?? [],
true
);
}
const navigationLists = new Map(
(state.navigationLists ?? []).map((navigationList) => [
navigationList.title,
navigationList.items
])
);
this.renderSummaryNavigationList(
container,
state,
"Invoked processes",
navigationLists.get("Invoked processes") ?? [],
true
);
this.renderScreenTransitionSummary(container, state);
this.renderSummaryNavigationList(
container,
state,
"Transitions / Outgoing screens",
navigationLists.get("Transitions / Outgoing screens") ?? [],
true
);
this.renderSummaryTable(container, state, tablesByTitle.get("Messages"), true);
if (state.sections.length > 0) {
const sections = this.createCollapsibleSection(
container,
"detectedSections",
this.t("summary.detectedSections"),
false
);
const list = sections.createEl("ul", { cls: "model-weave-summary-list" });
for (const section of state.sections) {
const item = list.createEl("li", {
text: this.localizeDetectedSectionLabel(section.label)
});
this.bindLocationNavigation(item, state.onNavigateToLocation, section);
}
}
}
renderSummaryTable(container, state, table, defaultOpen) {
if (!table) {
return;
}
const section = this.createCollapsibleSection(
container,
`summary:${table.title}`,
this.localizeSummarySectionTitle(table.title),
defaultOpen
);
section.addClass("model-weave-table-wrap");
const tableEl = section.createEl("table", {
cls: "model-weave-summary-table model-weave-data-table"
});
const thead = tableEl.createEl("thead");
const headRow = thead.createEl("tr");
for (const column of table.columns) {
headRow.createEl("th", {
text: column,
cls: "model-weave-summary-th"
});
}
const tbody = tableEl.createEl("tbody");
if (table.rows.length === 0) {
const emptyRow = tbody.createEl("tr");
emptyRow.createEl("td", {
text: this.t("summary.noRows"),
cls: "model-weave-summary-td model-weave-summary-empty-cell",
attr: { colspan: `${Math.max(1, table.columns.length)}` }
});
return;
}
for (const row of table.rows) {
const tr = tbody.createEl("tr");
if (row.line !== void 0) {
tr.addClass("model-weave-clickable");
}
this.bindLocationNavigation(tr, state.onNavigateToLocation, row);
for (const cell of row.cells) {
tr.createEl("td", {
text: cell,
cls: "model-weave-summary-td"
});
}
}
}
renderDetailCard(container, entries) {
const card = container.createDiv({ cls: "model-weave-detail-card" });
for (const entry of entries) {
const row = card.createDiv({ cls: "model-weave-detail-card-row" });
row.createDiv({
text: entry.label,
cls: "model-weave-detail-card-label"
});
row.createDiv({
text: entry.value,
cls: "model-weave-detail-card-value"
});
}
}
localizeSummaryCountLabel(label) {
const keyByLabel = {
Triggers: "summary.count.triggers",
Inputs: "summary.count.inputs",
Outputs: "summary.count.outputs",
Transitions: "summary.count.transitions",
Steps: "summary.count.steps",
Flows: "summary.count.flows",
Domains: "summary.count.domains",
"Domain Sources": "summary.count.domainSources",
Layouts: "summary.count.layouts",
Fields: "summary.count.fields",
Actions: "summary.count.actions",
Messages: "summary.count.messages",
"Local processes": "summary.count.localProcesses",
"Local Processes": "summary.count.localProcesses",
"Invoked processes": "summary.count.invokedProcesses",
"Invoked Processes": "summary.count.invokedProcesses",
"Outgoing screens": "summary.count.outgoingScreens"
};
const key = keyByLabel[label];
return key ? this.t(key) : label;
}
localizeSummarySectionTitle(title) {
const keyByTitle = {
Summary: "summary.section.summary",
"Domain Sources Summary": "summary.section.domainSourcesSummary",
"Domains Summary": "summary.section.domainsSummary",
"Triggers Summary": "summary.section.triggersSummary",
"Inputs Summary": "summary.section.inputsSummary",
"Outputs Summary": "summary.section.outputsSummary",
"Steps Summary": "summary.section.stepsSummary",
"Flows Summary": "summary.section.flowsSummary",
"Transitions Summary": "summary.section.transitionsSummary",
"Structure / Layout": "summary.section.structureLayout",
"UI Elements / Fields": "summary.section.uiElementsFields",
"Behavior / Actions": "summary.section.behaviorActions",
"Local processes": "summary.section.localProcesses",
"Local Processes": "summary.section.localProcesses",
"Invoked processes": "summary.section.invokedProcesses",
"Invoked Processes": "summary.section.invokedProcesses",
"Transitions / Outgoing screens": "summary.section.transitionsOutgoingScreens",
"Transitions / Outgoing Screens": "summary.section.transitionsOutgoingScreens",
"Transitions (legacy)": "summary.section.transitionsLegacy",
Layout: "summary.section.layout",
Fields: "summary.section.fields",
Actions: "summary.section.actions",
Messages: "summary.section.messages",
Notes: "summary.section.notes"
};
const key = keyByTitle[title];
return key ? this.t(key) : title;
}
localizeDetectedSectionLabel(label) {
const countMatch = label.match(/^(.+):\s+(\d+)\s+(rows|headings)$/);
if (countMatch) {
const [, rawName, rawCount, rawUnit] = countMatch;
const localizedName = this.localizeSummarySectionTitle(rawName);
const unitKey = rawUnit === "headings" ? "summary.unit.headings" : "summary.unit.rows";
return `${localizedName}: ${this.t(unitKey, { count: Number(rawCount) })}`;
}
const legacyMatch = label.match(/^Transitions \(legacy\):\s+(\d+)\s+rows$/);
if (legacyMatch) {
return `${this.localizeSummarySectionTitle("Transitions (legacy)")}: ${this.t("summary.unit.rows", { count: Number(legacyMatch[1]) })}`;
}
return this.localizeSummarySectionTitle(label);
}
renderSummaryNavigationList(container, state, title, items, defaultOpen) {
if (items.length === 0) {
return;
}
const section = this.createCollapsibleSection(
container,
`navigation:${title}`,
this.localizeSummarySectionTitle(title),
defaultOpen
);
const list = section.createEl("ul", { cls: "model-weave-summary-list" });
for (const itemInfo of items) {
const item = list.createEl("li", { text: itemInfo.label });
this.bindLocationNavigation(item, state.onNavigateToLocation, itemInfo);
}
}
renderScreenTransitionSummary(container, state) {
const transitions = state.screenPreviewTransitions ?? [];
if (transitions.length === 0) {
return;
}
const section = this.createCollapsibleSection(
container,
"screenTransitionStatus",
"Transition target status",
true
);
const list = section.createEl("ul", { cls: "model-weave-summary-list" });
for (const transition of transitions) {
const status = transition.selfTarget ? "self" : transition.unresolved ? "unresolved" : "resolved";
const item = list.createEl("li", {
text: `${transition.targetLabel}: ${status} (${transition.actions.length} action${transition.actions.length === 1 ? "" : "s"})`
});
item.title = transition.targetTitle ?? transition.targetLabel;
const firstAction = transition.actions.find(
(action) => typeof action.line === "number"
);
this.bindLocationNavigation(item, state.onNavigateToLocation, firstAction ?? {});
}
}
renderReviewSummaryPanel(container, options) {
const errors = options.warnings.filter((warning) => warning.severity === "error").length;
const warnings = options.warnings.filter((warning) => warning.severity === "warning").length;
const notes = options.warnings.filter((warning) => warning.severity === "info").length;
const modelName = getModelDisplayName(options.model);
const modelId = getModelId3(options.model);
const modelType = getModelType(options.model);
const sourceLinkCount = options.impactSummary?.relatedSourceLinks.length ?? options.sourceLinks?.length ?? 0;
const section = container.createDiv({
cls: "model-weave-preview-section model-weave-review-summary"
});
section.createEl("h3", {
text: this.t("review.summary.title"),
cls: "model-weave-preview-section-title"
});
const chips = section.createDiv({ cls: "model-weave-review-summary-chips" });
const addChip = (label, value, modifier) => {
const chip = chips.createDiv({ cls: "model-weave-review-summary-chip" });
if (modifier) {
chip.addClass(`model-weave-review-summary-chip-${modifier}`);
}
chip.createSpan({ text: label, cls: "model-weave-review-summary-chip-label" });
chip.createSpan({ text: String(value), cls: "model-weave-review-summary-chip-value" });
};
if (modelName) {
addChip(this.t("review.summary.model"), modelName);
}
if (modelType) {
addChip(this.t("review.summary.modelType"), modelType);
}
if (modelId && modelId !== modelName) {
addChip(this.t("review.summary.modelId"), modelId);
}
addChip(this.t("review.summary.errors"), errors, errors > 0 ? "error" : void 0);
addChip(this.t("review.summary.warnings"), warnings, warnings > 0 ? "warning" : void 0);
addChip(this.t("review.summary.notes"), notes);
if (options.impactSummary) {
addChip(this.t("review.summary.incoming"), options.impactSummary.inboundRelationships.length);
addChip(this.t("review.summary.outgoing"), options.impactSummary.outboundRelationships.length);
addChip(
this.t("review.summary.unresolved"),
options.impactSummary.unresolvedOutbound.length,
options.impactSummary.unresolvedOutbound.length > 0 ? "warning" : void 0
);
}
addChip(this.t("review.summary.sourceLinks"), sourceLinkCount);
addChip(
this.t("review.summary.weaveMap"),
options.weaveMapAvailable ? this.t("review.summary.available") : this.t("review.summary.notAvailable"),
options.weaveMapAvailable ? "available" : void 0
);
}
renderSourceLinksSection(container, sourceLinks) {
if (!sourceLinks?.some((sourceLink) => sourceLink.path.trim().length > 0)) {
return;
}
const sourceLinksSection = renderSourceLinks(
sourceLinks,
this.viewerPreferences.localSourceRoot,
this.viewerPreferences.uiLanguage
);
if (sourceLinksSection) {
container.appendChild(sourceLinksSection);
}
}
renderImpactSummarySection(container, summary, onCopyImpactSummary, onOpenImpactModel, weaveMapMermaidSource, colorScheme) {
if (!summary) {
return;
}
const section = container.createDiv({
cls: "model-weave-preview-section model-weave-impact-summary"
});
const header = section.createDiv({ cls: "model-weave-impact-summary-header" });
header.createEl("h3", {
text: this.t("relationship.title"),
cls: "model-weave-preview-section-title"
});
if (onCopyImpactSummary) {
const copyButton = header.createEl("button", {
text: this.t("relationship.copySummary"),
cls: "mod-cta model-weave-impact-copy-button"
});
copyButton.type = "button";
copyButton.addEventListener("click", (event) => {
event.preventDefault();
onCopyImpactSummary();
});
}
this.renderImpactOverviewCards(section, summary);
this.renderWeaveMapBlock(section, summary, weaveMapMermaidSource, colorScheme);
renderUsageViewSections(
section,
this.createImpactUsageSections(summary),
this.createUsageViewRendererOptions(onOpenImpactModel)
);
if (summary.modelType === "codeset") {
renderUsageViewSections(
section,
[this.createImpactValueUsageSection(summary)],
this.createUsageViewRendererOptions()
);
}
renderUsageDetailSection(
section,
"impactUnresolved",
this.t("relationship.unresolvedReferences"),
this.t("relationship.noUnresolved"),
summary.unresolvedOutbound.map(
(reference) => this.createImpactUnresolvedDetail(reference)
),
this.createUsageViewRendererOptions()
);
renderGroupedSourceLinkSection(
section,
"impactSourceLinks",
this.t("relationship.relatedSourceLinks"),
this.t("relationship.noRelatedSourceLinks"),
summary.relatedSourceLinks.map(
(sourceLink) => this.createGroupedSourceLink(sourceLink)
),
this.createUsageViewRendererOptions()
);
}
renderImpactOverviewCards(container, summary) {
const cards = container.createDiv({ cls: "model-weave-impact-overview-cards" });
const addCard = (label, value, description, modifier) => {
const card = cards.createDiv({ cls: "model-weave-impact-overview-card" });
if (modifier) {
card.addClass(`model-weave-impact-overview-card-${modifier}`);
}
card.createDiv({ text: label, cls: "model-weave-impact-overview-card-label" });
card.createDiv({ text: String(value), cls: "model-weave-impact-overview-card-value" });
card.createDiv({ text: description, cls: "model-weave-impact-overview-card-description" });
};
for (const group of this.getImpactRelationshipGroupCounts(summary.inboundRelationships, "incoming")) {
addCard(group.label, group.count, this.t("relationship.referencedByThisObject"));
}
for (const group of this.getImpactRelationshipGroupCounts(summary.outboundRelationships, "outgoing")) {
addCard(group.label, group.count, this.t("relationship.referencesFromThisObject"));
}
if (summary.unresolvedOutbound.length > 0) {
addCard(
this.t("relationship.overview.unresolved"),
summary.unresolvedOutbound.length,
this.t("relationship.unresolvedReferences"),
"warning"
);
}
if (summary.relatedSourceLinks.length > 0) {
addCard(
this.t("relationship.overview.sourceLinks"),
summary.relatedSourceLinks.length,
this.t("relationship.relatedSourceLinks")
);
}
if (summary.modelType === "codeset" && summary.valueUsages.length > 0) {
addCard(
this.t("relationship.overview.valueUsage"),
summary.valueUsages.length,
this.t("relationship.valueUsage")
);
}
}
renderWeaveMapBlock(container, summary, initialMermaidSource, colorScheme) {
let sourceLinkMode = "compact";
let weaveMapModel = this.buildWeaveMapModel(summary, sourceLinkMode);
let source = (weaveMapModel ? buildWeaveMapMermaidSource(weaveMapModel, { colorScheme }) : initialMermaidSource)?.trim();
if (!source) {
return;
}
let interactionTargets = weaveMapModel ? this.buildWeaveMapInteractionTargets(weaveMapModel, summary) : [];
const details = container.createEl("details", {
cls: "model-weave-preview-section model-weave-impact-weave-map"
});
details.open = this.getCollapsibleOpenState("impactWeaveMap", false);
details.addEventListener("toggle", () => {
this.setCollapsibleOpenState("impactWeaveMap", details.open);
if (details.open) {
renderWeaveMap();
}
});
details.createEl("summary", {
text: `${this.t("relationship.weaveMap.title")} \u2014 ${summary.modelId || summary.modelLabel}`,
cls: "model-weave-summary-heading model-weave-preview-section-title"
});
const section = details.createDiv({ cls: "model-weave-impact-weave-map-content" });
section.createEl("p", {
text: this.t("relationship.weaveMap.description"),
cls: "model-weave-muted"
});
const modeSelector = section.createDiv({
cls: "model-weave-render-mode-toolbar-host model-weave-impact-weave-map-mode"
});
modeSelector.createEl("span", {
text: this.t("relationship.weaveMap.viewMode"),
cls: "model-weave-summary-muted"
});
const modeButtons = /* @__PURE__ */ new Map();
const updateModeButtons = () => {
for (const [mode, button] of modeButtons) {
button.setAttribute("aria-pressed", String(sourceLinkMode === mode));
button.toggleClass("is-active", sourceLinkMode === mode);
}
};
const renderCurrentMode = () => {
weaveMapModel = this.buildWeaveMapModel(summary, sourceLinkMode);
source = weaveMapModel ? buildWeaveMapMermaidSource(weaveMapModel, { colorScheme }).trim() : void 0;
interactionTargets = weaveMapModel ? this.buildWeaveMapInteractionTargets(weaveMapModel, summary) : [];
if (!source) {
renderContainer.empty();
sourcePanelContainer.empty();
return;
}
rendered = false;
rendering = false;
renderContainer.empty();
sourcePanelContainer.empty();
renderWeaveMap();
};
for (const mode of ["compact", "full"]) {
const button = modeSelector.createEl("button", {
text: mode === "compact" ? this.t("relationship.weaveMap.compact") : this.t("relationship.weaveMap.full"),
cls: "model-weave-secondary-button"
});
button.type = "button";
modeButtons.set(mode, button);
button.addEventListener("click", (event) => {
event.preventDefault();
if (sourceLinkMode === mode) {
return;
}
sourceLinkMode = mode;
updateModeButtons();
renderCurrentMode();
});
}
updateModeButtons();
const renderContainer = section.createDiv({
cls: "model-weave-impact-weave-map-body"
});
renderContainer.setCssStyles({
height: "420px",
minHeight: "360px",
display: "flex",
flexDirection: "column",
position: "relative",
overflow: "hidden"
});
const sourcePanelContainer = section.createDiv({
cls: "model-weave-impact-weave-map-source"
});
let rendered = false;
let rendering = false;
const renderWeaveMap = () => {
const currentSource = source;
if (!currentSource || rendered || rendering) {
return;
}
rendering = true;
renderContainer.empty();
const shell3 = createMermaidShell({
className: "model-weave-impact-weave-map-render",
title: buildWeaveMapGraphTitle(this.t, summary),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportWeaveMapAsPng(renderContainer, summary.modelPath),
onExportAndOpenPng: () => this.exportWeaveMapAsPngAndOpen(renderContainer, summary.modelPath)
});
shell3.root.setCssStyles({
flex: "1 1 auto",
minHeight: "0",
width: "100%",
height: "100%"
});
shell3.canvas.setCssStyles({
minHeight: "0"
});
this.appendViewerToolbarControls(shell3.root, shell3.root);
renderContainer.appendChild(shell3.root);
sourcePanelContainer.empty();
void this.renderWeaveMapMermaid(shell3, currentSource, sourcePanelContainer, interactionTargets).then(
() => {
rendered = true;
rendering = false;
},
() => {
rendering = false;
}
);
};
if (details.open) {
renderWeaveMap();
}
}
buildWeaveMapModel(summary, sourceLinkMode) {
try {
return buildWeaveMapModel(summary, { sourceLinkMode });
} catch {
return void 0;
}
}
buildWeaveMapInteractionTargets(model, summary) {
const mermaidIds = createWeaveMapNodeMermaidIds(model.nodes);
return model.nodes.filter((node) => this.isResolvedWeaveMapModelNode(node)).map((node) => ({
nodeId: node.id,
mermaidId: mermaidIds.get(node.id) ?? node.id,
label: node.label,
filePath: node.path ?? "",
linktext: node.path || node.modelId || node.label,
sourcePath: summary.modelPath,
modelId: node.modelId,
modelType: node.modelType,
status: "resolved"
})).filter((target) => target.filePath.length > 0 && target.linktext.length > 0);
}
isResolvedWeaveMapModelNode(node) {
return (node.status === "focus" || node.status === "ok") && Boolean(node.path);
}
async renderWeaveMapMermaid(shell3, source, container, interactionTargets) {
try {
await this.waitForWeaveMapContainerReady(shell3.root);
await renderMermaidSourceIntoShell(shell3, {
source,
renderIdPrefix: "model_weave_impact_weave_map",
fitVerticalAlign: "top",
sourcePanelContainer: container,
sourcePanelPlacement: "append",
...getMermaidSourceLabels(this.t),
showRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
await this.waitForWeaveMapSvgReady(shell3.surface);
attachMermaidNodeInteractions({
app: this.app,
rootEl: shell3.surface,
targets: interactionTargets,
source: "model-weave",
nodeClassName: "model-weave-weave-map-interactive-node",
dragThreshold: 6,
isDebugEnabled: () => this.viewerPreferences.showMermaidRenderDebug === true,
debugName: "Weave Map",
formatTitle: (target) => target.modelId ? `${target.label ?? target.linktext} (${target.modelType ?? "model"} / ${target.modelId})` : target.label ?? target.linktext
});
await this.waitForNextAnimationFrame(shell3.root);
await this.waitForNextAnimationFrame(shell3.root);
shell3.toolbar?.fitButton.click();
} catch (error) {
shell3.root.addClass("model-weave-mermaid-fallback-shell");
shell3.surface.empty();
shell3.surface.createEl("p", {
text: error instanceof Error ? error.message : String(error),
cls: "model-weave-muted"
});
throw error;
}
}
async waitForWeaveMapSvgReady(surface) {
for (let index = 0; index < 6; index += 1) {
await this.waitForNextAnimationFrame(surface);
const svg2 = surface.querySelector("svg");
if (!svg2) {
continue;
}
const rect = svg2.getBoundingClientRect();
const width = Number(svg2.getAttribute("width") ?? 0);
const height = Number(svg2.getAttribute("height") ?? 0);
const hasMeasuredSize = Number.isFinite(width) && width > 0 && Number.isFinite(height) && height > 0 || rect.width > 0 && rect.height > 0;
if (hasMeasuredSize) {
return;
}
}
const svg = surface.querySelector("svg");
if (!svg) {
throw new Error("Mermaid SVG was not rendered.");
}
throw new Error("Mermaid SVG size could not be measured.");
}
async waitForWeaveMapContainerReady(element) {
for (let index = 0; index < 6; index += 1) {
await this.waitForNextAnimationFrame(element);
const rect = element.getBoundingClientRect();
if (element.isConnected && rect.width > 0 && rect.height > 0) {
return;
}
}
}
waitForNextAnimationFrame(element) {
const view = element.ownerDocument.defaultView;
return new Promise((resolve) => {
if (view?.requestAnimationFrame) {
view.requestAnimationFrame(() => resolve());
return;
}
(view ?? window).setTimeout(resolve, 0);
});
}
createImpactValueUsageSection(summary) {
return {
id: "impactValueUsage",
title: this.t("relationship.valueUsage"),
emptyText: this.t("relationship.noValueUsage"),
items: summary.valueUsages.map((valueUsage) => {
const usageCount = valueUsage.relationships.reduce(
(total, relationship) => total + relationship.usageCount,
0
);
return {
label: valueUsage.member,
usageCount,
summaryText: `${valueUsage.member} (${this.formatLocalizedCount(usageCount, "relationship.usage.one", "relationship.usage.other")})`,
details: valueUsage.relationships.flatMap(
(relationship) => relationship.usages.map(
(usage) => this.createImpactValueUsageDetail(relationship, usage)
)
),
sourceLinks: []
};
})
};
}
createImpactUsageSections(summary) {
return [
...this.createGroupedImpactUsageSections(
"incoming",
summary.inboundRelationships,
"impactInbound",
this.t("relationship.referencedByThisObject"),
this.t("relationship.noInbound")
),
...this.createGroupedImpactUsageSections(
"outgoing",
summary.outboundRelationships,
"impactOutbound",
this.t("relationship.referencesFromThisObject"),
this.t("relationship.noOutbound")
)
];
}
createGroupedImpactUsageSections(direction, relationships, idPrefix, emptyTitle, emptyText) {
if (relationships.length === 0) {
return [{ id: idPrefix, title: emptyTitle, emptyText, items: [] }];
}
return this.groupImpactRelationships(relationships).map(({ key, relationships: groupedRelationships }) => ({
id: `${idPrefix}:${key}`,
title: this.getImpactRelationshipGroupLabel(direction, key),
emptyText,
items: groupedRelationships.map((relationship) => ({
label: relationship.modelLabel,
type: relationship.modelType,
path: relationship.modelPath,
usageCount: relationship.usageCount,
openTargetPath: relationship.modelPath,
details: relationship.usages.map(
(usage) => this.createImpactRelationshipDetail(usage)
),
sourceLinks: relationship.sourceLinks.map(
(sourceLink) => this.createGroupedSourceLink(sourceLink)
)
}))
}));
}
getImpactRelationshipGroupCounts(relationships, direction) {
return this.groupImpactRelationships(relationships).map(({ key, relationships: groupedRelationships }) => ({
label: this.getImpactRelationshipGroupLabel(direction, key),
count: groupedRelationships.length
}));
}
groupImpactRelationships(relationships) {
const groups = /* @__PURE__ */ new Map();
for (const relationship of relationships) {
const key = this.getImpactRelationshipGroupKey(relationship);
const group = groups.get(key) ?? [];
group.push(relationship);
groups.set(key, group);
}
return IMPACT_RELATIONSHIP_CATEGORY_ORDER.map((key) => ({ key, relationships: groups.get(key) ?? [] })).filter((group) => group.relationships.length > 0);
}
getImpactRelationshipGroupKey(relationship) {
return getImpactRelationshipCategoryKey(relationship);
}
getImpactRelationshipGroupLabel(direction, key) {
const prefix = direction === "incoming" ? "relationship.usedBy" : "relationship.references";
const suffix = ["screens", "processes", "rules", "mappings", "diagrams", "classes", "dataEr"].includes(key) ? key : "other";
return this.t(`${prefix}.${suffix}`);
}
createImpactRelationshipDetail(reference) {
const location = [reference.section, reference.field].filter(Boolean).join(".");
return {
label: `${location ? `${location}: ` : ""}${reference.targetRaw}`,
meta: [reference.relationKind, reference.sourceContext].filter(Boolean).join("; "),
notes: reference.notes,
title: reference.notes ?? reference.targetPath ?? reference.targetRaw
};
}
createImpactValueUsageDetail(relationship, reference) {
const location = [reference.section, reference.field].filter(Boolean).join(".");
const meta = [relationship.modelType, location, reference.sourceContext].filter(Boolean).join("; ");
const label = `${relationship.modelLabel}${meta ? ` (${meta})` : ""}`;
return {
label,
notes: reference.notes,
title: reference.notes ?? reference.sourcePath
};
}
createImpactUnresolvedDetail(reference) {
const location = [reference.section, reference.field].filter(Boolean).join(".");
const meta = [reference.relationKind, location || null].filter(Boolean).join("; ");
return {
label: reference.targetRaw,
meta,
notes: reference.notes,
title: reference.notes ?? reference.targetRaw
};
}
createGroupedSourceLink(sourceLink) {
return {
relationKind: sourceLink.relationKind,
ownerLabel: sourceLink.ownerLabel,
ownerPath: sourceLink.ownerPath,
path: sourceLink.path,
notes: sourceLink.notes,
...sourceLink.label ? { label: sourceLink.label } : {}
};
}
createUsageViewRendererOptions(onOpenImpactModel) {
return {
openLabel: this.t("relationship.open"),
sourceLinkLabel: this.t("relationship.sourceLink"),
formatUsageCount: (count) => this.formatLocalizedCount(
count,
"relationship.usage.one",
"relationship.usage.other"
),
formatNoteCount: (count) => this.formatLocalizedCount(
count,
"relationship.note.one",
"relationship.note.other"
),
getOpenState: this.getCollapsibleOpenState,
setOpenState: this.setCollapsibleOpenState,
onOpenItem: onOpenImpactModel ?? void 0
};
}
createCollapsibleSection(container, key, title, defaultOpen) {
const details = container.createEl("details");
details.addClass("model-weave-preview-section");
details.dataset.modelWeaveSectionKey = key;
details.open = this.getCollapsibleOpenState(key, defaultOpen);
details.addEventListener("toggle", () => {
this.setCollapsibleOpenState(key, details.open);
});
const summary = details.createEl("summary", { text: title });
summary.addClass("model-weave-summary-heading");
summary.addClass("model-weave-preview-section-title");
return details.createDiv();
}
bindLocationNavigation(element, onNavigate, location) {
if (!onNavigate || typeof location.line !== "number") {
return;
}
element.tabIndex = 0;
element.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
onNavigate({ line: location.line, ch: location.ch });
};
element.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
event.stopPropagation();
onNavigate({ line: location.line, ch: location.ch });
}
};
}
formatLocalizedCount(count, singularKey, pluralKey) {
const key = count === 1 ? singularKey : pluralKey;
return `${count} ${this.t(key)}`;
}
resetImpactCollapsibleState() {
for (const key of [...this.collapsibleState.keys()]) {
if (key.startsWith("impact")) {
this.collapsibleState.delete(key);
}
}
}
persistCurrentScrollPosition() {
const filePath = this.getCurrentFilePath();
if (!filePath || !this.activeScrollContainer) {
return;
}
this.scrollStateByFilePath.set(filePath, this.activeScrollContainer.scrollTop);
}
restoreCurrentScrollPosition() {
const filePath = this.getCurrentFilePath();
if (!filePath || !this.activeScrollContainer) {
return;
}
const nextScrollTop = this.scrollStateByFilePath.get(filePath);
if (typeof nextScrollTop === "number") {
this.activeScrollContainer.scrollTop = nextScrollTop;
}
}
renderDfdObjectState(state) {
const shell3 = this.createViewerSplitShell(`dfd-object:${state.model.path}`, 0.62);
this.activeScrollContainer = shell3.bottomPane;
this.renderReviewSummaryPanel(shell3.bottomPane, {
model: state.model,
warnings: state.warnings,
impactSummary: state.impactSummary,
sourceLinks: state.model.sourceLinks,
weaveMapAvailable: Boolean(state.weaveMapMermaidSource)
});
renderDiagnostics(
shell3.bottomPane,
state.warnings,
state.onOpenDiagnostic ?? void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
const objectDetails = renderObjectModel(
state.model,
void 0,
this.viewerPreferences.localSourceRoot,
this.viewerPreferences.uiLanguage,
{ includeSourceLinks: false }
);
this.renderImpactSummarySection(
shell3.bottomPane,
state.impactSummary,
state.onCopyImpactSummary,
state.onOpenImpactModel,
state.weaveMapMermaidSource,
state.colorScheme
);
this.renderSourceLinksSection(shell3.bottomPane, state.model.sourceLinks);
shell3.bottomPane.appendChild(objectDetails);
const diagramRoot = renderDiagramModel(state.diagram, {
app: this.app,
interactionSourcePath: state.model.path,
hideTitle: true,
hideDetails: false,
fitVerticalAlign: "top",
onOpenObject: state.onOpenObject ?? void 0,
viewportState: this.objectGraphViewportState,
onViewportStateChange: this.createObjectViewportStateHandler(state.model.path),
sourcePanelContainer: shell3.bottomPane,
sourcePanelPlacement: "prepend",
...getMermaidSourceLabels(this.t),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportCurrentDiagramAsPngWithNotice(),
onExportAndOpenPng: () => this.exportCurrentDiagramAsPngAndOpenWithNotice(),
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(diagramRoot, buildGraphIdentityTitle(state.model));
this.appendViewerToolbarControls(diagramRoot);
this.moveDetailSections(diagramRoot, shell3.bottomPane);
shell3.topPane.appendChild(diagramRoot);
}
renderDiagramState(state) {
const filePath = state.diagram.diagram.path;
const shell3 = this.createViewerSplitShell(`diagram:${filePath}`, 0.64);
shell3.bottomPane.addClass("model-weave-collection-diagram-lower-pane");
const lowerSlots = this.createCollectionDiagramLowerPaneSlots(shell3.bottomPane);
this.activeScrollContainer = shell3.bottomPane;
this.renderReviewSummaryPanel(lowerSlots.review, {
model: state.diagram.diagram,
warnings: state.warnings,
impactSummary: state.impactSummary,
sourceLinks: state.diagram.diagram.sourceLinks,
weaveMapAvailable: Boolean(state.weaveMapMermaidSource)
});
renderDiagnostics(
lowerSlots.diagnostics,
state.warnings,
state.onOpenDiagnostic ?? void 0,
this.getCollapsibleOpenState,
this.setCollapsibleOpenState,
this.getDiagnosticLanguage(),
this.getDiagnosticQuickFixActions
);
const diagramRoot = renderDiagramModel(state.diagram, {
onOpenObject: state.onOpenObject ?? void 0,
app: this.app,
interactionSourcePath: filePath,
flowDiagramViewMode: state.diagram.diagram.schema === "flow_diagram" ? this.getFlowDiagramViewMode(state) : void 0,
renderMode: getStandardRenderMode(state.rendererSelection),
colorScheme: state.colorScheme,
viewportState: this.diagramViewportState,
onViewportStateChange: this.createDiagramViewportStateHandler(filePath),
sourcePanelContainer: lowerSlots.source,
...getMermaidSourceLabels(this.t),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportCurrentDiagramAsPngWithNotice(),
onExportAndOpenPng: () => this.exportCurrentDiagramAsPngAndOpenWithNotice(),
...getDfdDetailLabels(this.t),
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(diagramRoot, buildGraphIdentityTitle(state.diagram.diagram));
this.appendRendererSelection(diagramRoot, state.rendererSelection);
this.appendViewerToolbarControls(diagramRoot);
if (isFlowDiagramViewSelectorVisible(state.diagram)) {
this.appendFlowDiagramViewSelector(diagramRoot, filePath);
}
this.moveDetailSections(diagramRoot, lowerSlots.details);
this.renderImpactSummarySection(
lowerSlots.impact,
state.impactSummary,
state.onCopyImpactSummary,
state.onOpenImpactModel,
state.weaveMapMermaidSource,
state.colorScheme
);
this.renderSourceLinksSection(lowerSlots.sourceLinks, state.diagram.diagram.sourceLinks);
const appliedColorSchemeSlot = getAppliedColorSchemeLowerPaneSlot(
state.diagram.diagram.schema
);
this.renderAppliedColorScheme(
lowerSlots[appliedColorSchemeSlot],
state.colorScheme,
this.getImpactColorSchemeTargets(
this.getDiagramColorSchemeTargets(state.diagram),
state.impactSummary
)
);
shell3.topPane.appendChild(diagramRoot);
}
getDiagramColorSchemeTargets(diagram) {
return getDfdMermaidColorSchemeTargets(diagram);
}
getImpactColorSchemeTargets(baseTargets, impactSummary) {
return impactSummary ? [...baseTargets, "weave_map"] : baseTargets;
}
applyLowerPanelTabs() {
const panes = Array.from(
this.contentEl.querySelectorAll(".model-weave-viewer-lower-pane")
);
for (const child of Array.from(this.contentEl.children)) {
if (child.instanceOf(HTMLElement) && child.matches(".model-weave-summary-section.model-weave-summary-details")) {
panes.push(child);
}
}
for (const pane of panes) {
this.applyLowerPanelTabsToPane(pane);
}
}
applyLowerPanelTabsToPane(container) {
if (container.querySelector(":scope > .model-weave-lower-tabs")) {
return;
}
const slots = this.collectLowerPanelSlots(container);
const tabCandidates = this.getLowerPanelTabCandidates();
const allTabs = [
{
id: "details",
label: this.t("viewer.lowerTab.details"),
panel: this.combineLowerPanelSlots(container, [slots.review, slots.details], "details")
},
{
id: "relationships",
label: this.t("viewer.lowerTab.relationships"),
panel: slots.impact
},
{
id: "diagnostics",
label: this.t("viewer.lowerTab.diagnostics"),
panel: slots.diagnostics
},
{
id: "source-links",
label: this.t("viewer.lowerTab.sourceLinks"),
panel: slots.sourceLinks
},
{
id: "mermaid",
label: this.t("viewer.lowerTab.mermaid"),
panel: slots.source
}
];
const tabs = tabCandidates.length > 0 ? tabCandidates.flatMap((tabId) => {
const tab = allTabs.find((candidate) => candidate.id === tabId);
if (!tab) {
return [];
}
this.ensureLowerPanelTabContent(tab);
return [tab];
}) : allTabs.filter((tab) => this.hasLowerPanelContent(tab.panel));
if (tabs.length === 0) {
return;
}
container.empty();
const activeId = this.resolveActiveLowerPanelTab(tabs);
const root = container.createDiv({ cls: "model-weave-lower-tabs" });
const tabBar = root.createDiv({ cls: "model-weave-lower-tab-bar" });
tabBar.setAttribute("role", "tablist");
const panelsRoot = root.createDiv({ cls: "model-weave-lower-tab-panels" });
const activateTab = (tabId) => {
this.activeLowerPanelTabId = tabId;
for (const button of Array.from(tabBar.children)) {
if (!button.instanceOf(HTMLElement)) {
continue;
}
const isActive = button.dataset.modelWeaveLowerTab === tabId;
button.toggleClass("is-active", isActive);
button.setAttribute("aria-selected", String(isActive));
button.tabIndex = isActive ? 0 : -1;
}
for (const panel of Array.from(panelsRoot.children)) {
if (!panel.instanceOf(HTMLElement)) {
continue;
}
const isActive = panel.dataset.modelWeaveLowerPanel === tabId;
panel.toggleClass("is-active", isActive);
panel.toggleAttribute("hidden", !isActive);
}
};
for (const tab of tabs) {
const tabButton = tabBar.createEl("button", {
text: tab.label,
cls: "model-weave-lower-tab-button"
});
const tabDomId = `${this.lowerPanelDomIdPrefix}-tab-${tab.id}`;
const panelDomId = `${this.lowerPanelDomIdPrefix}-panel-${tab.id}`;
tabButton.type = "button";
tabButton.dataset.modelWeaveLowerTab = tab.id;
tabButton.setAttribute("role", "tab");
tabButton.setAttribute("id", tabDomId);
tabButton.setAttribute("aria-controls", panelDomId);
tabButton.addEventListener("click", (event) => {
event.preventDefault();
activateTab(tab.id);
});
const panel = panelsRoot.createDiv({ cls: "model-weave-lower-tab-panel" });
panel.dataset.modelWeaveLowerPanel = tab.id;
panel.setAttribute("role", "tabpanel");
panel.setAttribute("id", panelDomId);
panel.setAttribute("aria-labelledby", tabDomId);
tab.panel.addClass("model-weave-lower-tab-content");
panel.appendChild(tab.panel);
}
activateTab(activeId);
}
getLowerPanelTabCandidates() {
switch (this.state.mode) {
case "object":
case "dfd-object":
case "diagram":
case "domains":
case "domain-diagram":
return ["details", "relationships", "diagnostics", "source-links", "mermaid"];
case "summary":
return this.state.businessFlow ? ["details", "relationships", "diagnostics", "source-links", "mermaid"] : ["details", "relationships", "diagnostics", "source-links"];
default:
return [];
}
}
ensureLowerPanelTabContent(tab) {
if (this.hasLowerPanelContent(tab.panel)) {
return;
}
tab.panel.createEl("p", {
text: this.getLowerPanelEmptyMessage(tab.id),
cls: "model-weave-lower-tab-empty model-weave-summary-muted"
});
}
getLowerPanelEmptyMessage(tabId) {
switch (tabId) {
case "relationships":
return this.t("viewer.lowerTab.empty.relationships");
case "diagnostics":
return this.t("viewer.lowerTab.empty.diagnostics");
case "source-links":
return this.t("viewer.lowerTab.empty.sourceLinks");
case "mermaid":
return this.t("viewer.lowerTab.empty.mermaid");
case "details":
default:
return this.t("viewer.lowerTab.empty.details");
}
}
collectLowerPanelSlots(container) {
const slots = this.createDetachedLowerPanelSlots(container);
const existingSlots = Array.from(
container.querySelectorAll(":scope > .model-weave-lower-pane-slot")
);
for (const slot of existingSlots) {
if (slot.classList.contains("model-weave-lower-pane-review-slot")) {
slots.review = slot;
} else if (slot.classList.contains("model-weave-lower-pane-diagnostics-slot")) {
slots.diagnostics = slot;
} else if (slot.classList.contains("model-weave-lower-pane-impact-slot")) {
slots.impact = slot;
} else if (slot.classList.contains("model-weave-lower-pane-source-links-slot")) {
slots.sourceLinks = slot;
} else if (slot.classList.contains("model-weave-lower-pane-details-slot")) {
slots.details = slot;
} else if (slot.classList.contains("model-weave-lower-pane-source-slot")) {
slots.source = slot;
}
}
if (existingSlots.length > 0) {
return slots;
}
for (const child of Array.from(container.children)) {
if (!child.instanceOf(HTMLElement)) {
continue;
}
this.getLowerPanelSlotForElement(child, slots).appendChild(child);
}
return slots;
}
createDetachedLowerPanelSlots(container) {
const doc = container.ownerDocument;
const createSlot = (slotClass) => {
const slot = doc.createElement("div");
slot.addClass("model-weave-lower-pane-slot");
slot.addClass(slotClass);
return slot;
};
return {
review: createSlot("model-weave-lower-pane-review-slot"),
diagnostics: createSlot("model-weave-lower-pane-diagnostics-slot"),
impact: createSlot("model-weave-lower-pane-impact-slot"),
sourceLinks: createSlot("model-weave-lower-pane-source-links-slot"),
details: createSlot("model-weave-lower-pane-details-slot"),
source: createSlot("model-weave-lower-pane-source-slot")
};
}
getLowerPanelSlotForElement(element, slots) {
if (element.matches(".model-weave-diagnostics-panel-summary, .model-weave-diagnostics-bulk-actions, .model-weave-diagnostics-details")) {
return slots.diagnostics;
}
if (element.matches(".model-weave-source-links")) {
return slots.sourceLinks;
}
if (element.matches(".model-weave-mermaid-source-panel, .model-weave-mermaid-render-debug")) {
return slots.source;
}
if (this.isLowerPanelRelationshipElement(element)) {
return slots.impact;
}
return slots.details;
}
isLowerPanelRelationshipElement(element) {
if (element.matches(".model-weave-impact-summary, .model-weave-object-context-list, .mdspec-related-list")) {
return true;
}
const sectionKey = element.dataset.modelWeaveSectionKey;
return sectionKey === "domains:relationships" || sectionKey === "relatedReferences";
}
combineLowerPanelSlots(container, slots, slotName) {
const combined = container.ownerDocument.createElement("div");
combined.addClass("model-weave-lower-pane-slot");
combined.addClass(`model-weave-lower-pane-${slotName}-slot`);
for (const slot of slots) {
while (slot.firstChild) {
combined.appendChild(slot.firstChild);
}
}
return combined;
}
hasLowerPanelContent(panel) {
return Array.from(panel.children).some(
(child) => child.instanceOf(HTMLElement) && !child.hasClass("model-weave-lower-tabs")
);
}
resolveActiveLowerPanelTab(tabs) {
if (this.activeLowerPanelTabId && tabs.some((tab) => tab.id === this.activeLowerPanelTabId)) {
return this.activeLowerPanelTabId;
}
if (tabs.some((tab) => tab.id === "diagnostics") && this.state.warnings.some(
(diagnostic) => normalizeDiagnosticSeverityForViewer(diagnostic) === "error" || normalizeDiagnosticSeverityForViewer(diagnostic) === "warning"
)) {
return "diagnostics";
}
return tabs.find((tab) => tab.id === "details")?.id ?? tabs[0].id;
}
createCollectionDiagramLowerPaneSlots(container) {
const review = container.createDiv({
cls: "model-weave-lower-pane-slot model-weave-lower-pane-review-slot"
});
const diagnostics = container.createDiv({
cls: "model-weave-lower-pane-slot model-weave-lower-pane-diagnostics-slot"
});
const impact = container.createDiv({
cls: "model-weave-lower-pane-slot model-weave-lower-pane-impact-slot"
});
const sourceLinks = container.createDiv({
cls: "model-weave-lower-pane-slot model-weave-lower-pane-source-links-slot"
});
const details = container.createDiv({
cls: "model-weave-lower-pane-slot model-weave-lower-pane-details-slot"
});
const source = container.createDiv({
cls: "model-weave-lower-pane-slot model-weave-lower-pane-source-slot"
});
return { review, diagnostics, impact, sourceLinks, details, source };
}
moveDetailSections(source, target) {
let detailWrapper = target.matches(".model-weave-lower-scroll, .model-weave-lower-pane-slot") ? target : target.querySelector(".model-weave-lower-scroll");
if (!detailWrapper) {
detailWrapper = target.createDiv({ cls: "model-weave-lower-scroll" });
}
const details = Array.from(source.children).filter(
(child) => child.instanceOf(HTMLElement) && child.matches(
"details, .mdspec-related-list, .model-weave-object-context-list"
)
).filter((child) => child.instanceOf(HTMLElement));
for (const detail of details) {
detail.remove();
detail.addClass("model-weave-detail-panel");
detailWrapper.appendChild(detail);
}
}
appendViewerToolbarControls(container, viewOnlyTarget = container) {
this.appendViewOnlyControl(container, viewOnlyTarget);
}
appendViewerFocusToolbar() {
const toolbar = this.contentEl.createDiv({
cls: "model-weave-viewer-toolbar"
});
const currentFilePath = this.getCurrentFilePath();
if (currentFilePath) {
const openGroup = toolbar.createDiv({ cls: "model-weave-preview-pane-actions" });
if (this.paneActions.onOpenPreviewInMainPane) {
const mainPaneButton = openGroup.createEl("button", {
text: this.t("preview.openInMainPane.short"),
cls: "model-weave-secondary-button model-weave-preview-pane-button"
});
mainPaneButton.type = "button";
mainPaneButton.title = this.t("preview.openInMainPane");
mainPaneButton.addEventListener("click", (event) => {
event.preventDefault();
this.paneActions.onOpenPreviewInMainPane?.(currentFilePath);
});
}
if (this.paneActions.onOpenPreviewInNewPane) {
const newPaneButton = openGroup.createEl("button", {
text: this.t("preview.openInNewPane.short"),
cls: "model-weave-secondary-button model-weave-preview-pane-button"
});
newPaneButton.type = "button";
newPaneButton.title = this.t("preview.openInNewPane");
newPaneButton.addEventListener("click", (event) => {
event.preventDefault();
this.paneActions.onOpenPreviewInNewPane?.(currentFilePath);
});
}
}
const button = toolbar.createEl("button", {
cls: "model-weave-secondary-button model-weave-focus-mode-button"
});
button.type = "button";
this.updateFocusModeButton(button);
button.addEventListener("click", (event) => {
event.preventDefault();
this.setFocusMode(!this.focusModeEnabled);
});
}
appendViewOnlyControl(container, viewOnlyTarget) {
const toolbar = container.querySelector(".mdspec-zoom-toolbar");
if (!toolbar) {
return;
}
const controls = toolbar.querySelector(".model-weave-zoom-toolbar-controls");
if (!controls || controls.querySelector(".model-weave-view-only-button")) {
return;
}
const button = container.ownerDocument.createElement("button");
button.type = "button";
button.addClass("model-weave-zoom-toolbar-button");
button.addClass("model-weave-view-only-button");
this.updateViewOnlyButton(button, viewOnlyTarget);
button.addEventListener("click", (event) => {
event.preventDefault();
this.setViewOnlyMode(
!(this.viewOnlyEnabled && this.viewOnlyTarget === viewOnlyTarget),
{ target: viewOnlyTarget }
);
});
const exportButton = controls.querySelector(
".model-weave-zoom-toolbar-export-png"
);
controls.insertBefore(button, exportButton ?? null);
}
updateFocusModeButton(button) {
const label = this.focusModeEnabled ? this.t("graph.focusModeExit") : this.t("graph.focusModeEnter");
button.textContent = this.focusModeEnabled ? "Exit" : "Focus";
button.setAttribute("aria-label", label);
button.title = label;
button.toggleClass("is-active", this.focusModeEnabled);
}
updateViewOnlyButton(button, target) {
const isActive = this.viewOnlyEnabled && Boolean(this.viewOnlyTarget?.contains(button));
const label = isActive ? this.t("graph.viewOnlyExit") : this.t("graph.viewOnlyEnter");
button.textContent = isActive ? "Exit View" : "View";
button.setAttribute("aria-label", label);
button.title = label;
button.toggleClass("is-active", isActive);
}
setFocusMode(enabled, options) {
if (this.focusModeEnabled === enabled) {
return;
}
this.focusModeEnabled = enabled;
this.contentEl.classList.toggle("model-weave-viewer-focus-mode", enabled);
if (enabled) {
this.attachFocusModeOverlay();
} else {
this.detachFocusModeOverlay();
}
this.contentEl.querySelectorAll(".model-weave-focus-mode-button").forEach((button) => this.updateFocusModeButton(button));
if (!options?.skipFit) {
this.scheduleActiveGraphFit();
}
}
setViewOnlyMode(enabled, options) {
const target = enabled ? options?.target ?? this.viewOnlyTarget : null;
if (enabled && !target) {
return;
}
this.detachViewOnlyTarget();
this.viewOnlyEnabled = enabled;
this.viewOnlyTarget = target;
if (enabled && target && !this.attachViewOnlyTarget(target)) {
this.viewOnlyEnabled = false;
this.viewOnlyTarget = null;
}
this.contentEl.classList.toggle(
"model-weave-viewer-view-only",
this.viewOnlyEnabled
);
this.contentEl.querySelectorAll(".model-weave-view-only-button").forEach((button) => this.updateViewOnlyButton(button, button));
if (!options?.skipFit) {
this.scheduleActiveGraphFit();
}
}
attachViewOnlyTarget(target) {
const parent = target.parentNode;
if (!parent) {
return false;
}
const stage = this.ensureViewOnlyStage();
const placeholder = target.ownerDocument.createComment(
"model-weave-view-only-placeholder"
);
parent.insertBefore(placeholder, target);
target.addClass("model-weave-view-only-target");
stage.appendChild(target);
this.viewOnlyPlaceholder = placeholder;
return true;
}
detachViewOnlyTarget() {
const target = this.viewOnlyTarget;
const placeholder = this.viewOnlyPlaceholder;
target?.removeClass("model-weave-view-only-target");
if (target && placeholder?.parentNode) {
placeholder.parentNode.insertBefore(target, placeholder);
placeholder.remove();
}
this.viewOnlyPlaceholder = null;
}
ensureViewOnlyStage() {
if (this.viewOnlyStage && this.viewOnlyStage.parentElement === this.contentEl) {
return this.viewOnlyStage;
}
const stage = this.contentEl.createDiv({
cls: "model-weave-view-only-stage"
});
this.viewOnlyStage = stage;
return stage;
}
attachFocusModeOverlay() {
if (this.focusModePlaceholder) {
return;
}
const parent = this.contentEl.parentNode;
if (!parent) {
return;
}
const doc = this.contentEl.ownerDocument;
const placeholder = doc.createComment("model-weave-focus-mode-placeholder");
parent.insertBefore(placeholder, this.contentEl);
this.contentEl.setCssProps({
"--mw-focus-overlay-top": this.getFocusOverlayTopOffset()
});
doc.body.appendChild(this.contentEl);
doc.body.classList.add("model-weave-focus-mode-active");
this.focusModePlaceholder = placeholder;
}
getFocusOverlayTopOffset() {
const titlebar = this.contentEl.ownerDocument.querySelector(
".titlebar"
);
const rect = titlebar?.getBoundingClientRect();
if (!rect || rect.height <= 0) {
return "0px";
}
return Math.max(0, Math.ceil(rect.bottom)).toString() + "px";
}
detachFocusModeOverlay() {
const placeholder = this.focusModePlaceholder;
const doc = this.contentEl.ownerDocument;
this.contentEl.setCssProps({
"--mw-focus-overlay-top": "0px"
});
if (placeholder?.parentNode) {
placeholder.parentNode.insertBefore(this.contentEl, placeholder);
placeholder.remove();
}
this.focusModePlaceholder = null;
this.removeFocusModeBodyClassIfUnused(doc);
}
removeFocusModeBodyClassIfUnused(doc) {
if (doc.querySelector(".model-weave-viewer-focus-mode")) {
return;
}
doc.body.classList.remove("model-weave-focus-mode-active");
}
scheduleActiveGraphFit() {
const view = this.contentEl.ownerDocument.defaultView;
if (!view) {
return;
}
view.requestAnimationFrame(() => {
view.requestAnimationFrame(() => {
this.contentEl.querySelectorAll(".model-weave-zoom-toolbar-fit").forEach((button) => button.click());
});
});
}
appendRendererSelection(container, selection) {
if (!selection || !selection.onSelectMode || (selection.supportedModes?.length ?? 0) < 2) {
return;
}
const toolbar = container.querySelector(".mdspec-zoom-toolbar");
if (!toolbar) {
return;
}
toolbar.addClass("model-weave-render-mode-toolbar-host");
toolbar.querySelector(".mdspec-renderer-select-group")?.remove();
const doc = container.ownerDocument;
const wrapper = doc.createElement("div");
wrapper.className = "mdspec-renderer-select-group model-weave-render-mode-row";
const title = doc.createElement("span");
title.addClass("model-weave-render-mode-label");
title.textContent = "Renderer";
const meta = doc.createElement("span");
meta.textContent = `selected ${selection.selectedMode} / effective ${selection.effectiveMode} / source ${selection.source}`;
if (selection.fallbackReason) {
meta.textContent += ` / ${selection.fallbackReason}`;
}
title.title = meta.textContent;
wrapper.appendChild(title);
const select = doc.createElement("select");
select.addClass("model-weave-render-mode-select");
select.title = meta.textContent;
for (const mode of selection.supportedModes) {
const option = doc.createElement("option");
option.value = mode;
option.textContent = this.formatRenderModeLabel(mode);
option.selected = mode === selection.visibleSelectedMode;
select.appendChild(option);
}
select.addEventListener("change", () => {
const selectedMode = selection.supportedModes.find((mode) => mode === select.value);
if (selectedMode) {
selection.onSelectMode?.(selectedMode);
}
});
wrapper.appendChild(select);
const rightGroup = toolbar.querySelector(".model-weave-zoom-toolbar-right") ?? toolbar;
rightGroup.appendChild(wrapper);
}
formatRenderModeLabel(mode) {
return mode.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
}
createViewerSplitShell(key, defaultTopRatio) {
const density = this.getDensitySpacing();
const root = this.contentEl.createDiv();
root.addClass("model-weave-viewer-split-shell");
const topPane = root.createDiv();
topPane.addClass("model-weave-viewer-upper-pane");
topPane.setCssProps({
"--mw-pane-padding": `${density.topPanePadding}px`,
"--mw-pane-gap": `${density.topPaneGap}px`
});
const handle = root.createDiv();
handle.addClass("model-weave-viewer-resize-handle");
const grip = handle.createDiv();
grip.addClass("model-weave-viewer-resize-grip");
const bottomPane = root.createDiv();
bottomPane.addClass("model-weave-viewer-lower-pane");
bottomPane.addClass("model-weave-viewer-lower-scroll");
bottomPane.setCssProps({
"--mw-pane-padding": `${density.bottomPanePadding}px ${density.bottomPanePadding + 2}px ${density.bottomPanePadding + 4}px`,
"--mw-pane-gap": `${density.bottomPaneGap}px`
});
const minTop = 180;
const minBottom = 180;
const clampRatio = (ratio) => Math.min(0.8, Math.max(0.3, ratio));
const applyRatio = (ratio) => {
const bounded = clampRatio(ratio);
const rootHeight = root.getBoundingClientRect().height;
const available = rootHeight > 0 ? Math.max(rootHeight - 10, minTop + minBottom) : 0;
if (available <= 0) {
topPane.setCssProps({ "--mw-pane-flex": `${bounded} 1 0` });
bottomPane.setCssProps({ "--mw-pane-flex": `${1 - bounded} 1 0` });
this.splitRatioByKey.set(key, bounded);
return;
}
const topPixels = Math.max(
minTop,
Math.min(available - minBottom, Math.round(available * bounded))
);
const bottomPixels = Math.max(minBottom, available - topPixels);
topPane.setCssProps({ "--mw-pane-flex": `0 0 ${topPixels}px` });
bottomPane.setCssProps({ "--mw-pane-flex": `0 0 ${bottomPixels}px` });
this.splitRatioByKey.set(key, topPixels / available);
};
const initialRatio = clampRatio(
this.splitRatioByKey.get(key) ?? defaultTopRatio
);
applyRatio(initialRatio);
const resizeObserver = new ResizeObserver(() => {
applyRatio(this.splitRatioByKey.get(key) ?? initialRatio);
});
resizeObserver.observe(root);
handle.addEventListener("pointerdown", (event) => {
event.preventDefault();
event.stopPropagation();
const pointerId = event.pointerId;
handle.setPointerCapture(pointerId);
const rootRect = root.getBoundingClientRect();
const available = Math.max(rootRect.height - 10, minTop + minBottom);
const onMove = (moveEvent) => {
const offset = moveEvent.clientY - rootRect.top;
const topPixels = Math.max(
minTop,
Math.min(available - minBottom, offset)
);
applyRatio(topPixels / available);
};
const onUp = () => {
handle.releasePointerCapture(pointerId);
handle.removeEventListener("pointermove", onMove);
handle.removeEventListener("pointerup", onUp);
handle.removeEventListener("pointercancel", onUp);
};
handle.addEventListener("pointermove", onMove);
handle.addEventListener("pointerup", onUp);
handle.addEventListener("pointercancel", onUp);
});
return { root, topPane, bottomPane };
}
getFontSizeVariables() {
switch (this.viewerPreferences.fontSize) {
case "small":
return {
base: "12px",
small: "11px",
large: "13px",
title: "15px"
};
case "large":
return {
base: "17px",
small: "15px",
large: "19px",
title: "20px"
};
default:
return {
base: "14px",
small: "12px",
large: "16px",
title: "17px"
};
}
}
getDensitySpacing() {
switch (this.viewerPreferences.nodeDensity) {
case "compact":
return {
contentGap: 8,
topPanePadding: 8,
topPaneGap: 8,
bottomPanePadding: 8,
bottomPaneGap: 10
};
case "relaxed":
return {
contentGap: 12,
topPanePadding: 12,
topPaneGap: 12,
bottomPanePadding: 12,
bottomPaneGap: 14
};
default:
return {
contentGap: 10,
topPanePadding: 10,
topPaneGap: 10,
bottomPanePadding: 10,
bottomPaneGap: 12
};
}
}
};
var SCREEN_CANVAS_PADDING = 48;
var SCREEN_MIN_ZOOM = 0.45;
var SCREEN_MAX_ZOOM = 2.4;
var SCREEN_INITIAL_ZOOM = 1;
var SCREEN_BOX_WIDTH = 420;
var SCREEN_BOX_HEADER_HEIGHT = 42;
var SCREEN_SECTION_HEADER_HEIGHT = 24;
var SCREEN_SECTION_PADDING = 10;
var SCREEN_SECTION_GAP = 8;
var SCREEN_FIELD_ROW_HEIGHT = 22;
var SCREEN_MAX_TITLE_CHARS = 34;
var SCREEN_MAX_SECTION_CHARS = 36;
var SCREEN_MAX_FIELD_CHARS = 40;
var SCREEN_TRANSITION_LANE_WIDTH = 168;
var SCREEN_TARGET_BOX_WIDTH = 240;
var SCREEN_TARGET_BOX_MIN_HEIGHT = 96;
var SCREEN_TARGET_BOX_HEADER_VERTICAL_PADDING = 16;
var SCREEN_TARGET_BOX_BODY_VERTICAL_PADDING = 20;
var SCREEN_TARGET_KIND_LINE_HEIGHT = 16;
var SCREEN_TARGET_TITLE_LINE_HEIGHT = 22;
var SCREEN_TARGET_ROW_LINE_HEIGHT = 16;
var SCREEN_TARGET_ROW_GAP = 4;
var SCREEN_TARGET_BOX_GAP = 24;
var SCREEN_LABEL_PILL_WIDTH = 132;
var SCREEN_LABEL_PILL_HEIGHT = 24;
var SCREEN_LABEL_PILL_GAP = 8;
var SCREEN_ARROW_COLOR = "#64748b";
function buildScreenPreviewData(state, t) {
return {
title: state.title,
sourcePath: state.filePath,
blocks: localizeScreenPreviewBlocks(state.layoutBlocks ?? [], t),
transitions: state.screenPreviewTransitions ?? []
};
}
function localizeScreenPreviewBlocks(blocks, t) {
if (!t) {
return blocks;
}
return blocks.map((block) => ({
...block,
label: block.label === "Unassigned" || block.label === LEGACY_SCREEN_UNASSIGNED_LABEL ? t("screen.preview.unassigned") : block.label,
subtitle: block.subtitle === "Layout is missing or undefined" || block.subtitle === LEGACY_SCREEN_LAYOUT_MISSING_SUBTITLE ? t("screen.preview.layoutMissing") : block.subtitle
}));
}
var LEGACY_SCREEN_UNASSIGNED_LABEL = "\u672A\u5206\u985E [unassigned]";
var LEGACY_SCREEN_LAYOUT_MISSING_SUBTITLE = "layout \u672A\u6307\u5B9A\u307E\u305F\u306F\u672A\u5B9A\u7FA9";
function createScreenPreviewDiagram(data, options) {
const root = activeDocument.createElement("section");
root.className = "mdspec-diagram mdspec-diagram--screen";
root.addClass("model-weave-screen-preview");
root.addClass("model-weave-screen-chart");
const scene = buildScreenPreviewScene(data);
const canvas = activeDocument.createElement("div");
canvas.className = "mdspec-screen-canvas";
canvas.addClass("model-weave-screen-preview-layout-block");
if (!options?.forExport) {
canvas.addClass("model-weave-screen-preview-layout-block-interactive");
}
const toolbar = options?.forExport ? null : createZoomToolbar("Ctrl/Meta + wheel: zoom / Drag background: pan");
if (toolbar) {
root.appendChild(toolbar.root);
}
const viewport = activeDocument.createElement("div");
viewport.className = "mdspec-screen-viewport";
viewport.addClass("model-weave-screen-preview-viewport");
const surface = activeDocument.createElement("div");
surface.className = "mdspec-screen-surface";
surface.dataset.modelWeaveExportSurface = "true";
surface.dataset.modelWeaveRenderer = "custom";
surface.dataset.modelWeaveSceneWidth = `${scene.width}`;
surface.dataset.modelWeaveSceneHeight = `${scene.height}`;
surface.addClass("model-weave-screen-preview-surface");
surface.setCssProps({
"--mw-scene-width": `${scene.width}px`,
"--mw-scene-height": `${scene.height}px`
});
surface.appendChild(createScreenPreviewTransitionSvg(scene));
surface.appendChild(
createScreenPreviewMainBox(data, scene.mainBoxHeight, scene.mainBoxTop, options)
);
for (const target of scene.targets) {
surface.appendChild(createScreenPreviewTargetBox(target, options));
}
for (const target of scene.targets) {
for (const pill of target.labelPills) {
surface.appendChild(createScreenPreviewActionPill(pill, options?.onNavigateToLocation));
}
}
viewport.appendChild(surface);
canvas.appendChild(viewport);
root.appendChild(canvas);
if (toolbar) {
attachGraphViewportInteractions(canvas, surface, toolbar, scene, {
minZoom: SCREEN_MIN_ZOOM,
maxZoom: SCREEN_MAX_ZOOM,
initialZoom: SCREEN_INITIAL_ZOOM,
fitVerticalAlign: "top",
fitContentBounds: {
top: scene.contentTop,
bottom: scene.contentBottom
},
nodeSelector: ".model-weave-screen-card, .model-weave-screen-transition-label, .model-weave-screen-preview-card, .model-weave-screen-preview-target-box, .model-weave-screen-preview-edge-label",
viewportState: options?.viewportState,
onViewportStateChange: options?.onViewportStateChange
});
}
return root;
}
function buildScreenPreviewScene(data) {
const blocks = data.blocks.length > 0 ? data.blocks : [{ label: "Unassigned", items: [] }];
const mainBoxHeight = SCREEN_BOX_HEADER_HEIGHT + blocks.reduce((sum, block) => sum + measureScreenPreviewBlockHeight(block), 0) + Math.max(0, blocks.length - 1) * SCREEN_SECTION_GAP;
const targetGroups = data.transitions;
const targetHeights = targetGroups.map((target) => {
const targetBoxHeight = measureScreenPreviewTargetBoxHeight(target);
const labelsHeight = target.actions.length * SCREEN_LABEL_PILL_HEIGHT + Math.max(0, target.actions.length - 1) * SCREEN_LABEL_PILL_GAP;
return Math.max(
targetBoxHeight,
labelsHeight + SCREEN_SECTION_PADDING * 2
);
});
const targetStackHeight = targetHeights.reduce((sum, currentHeight) => sum + currentHeight, 0) + Math.max(0, targetHeights.length - 1) * SCREEN_TARGET_BOX_GAP;
const contentHeight = Math.max(mainBoxHeight, targetStackHeight);
const mainBoxTop = SCREEN_CANVAS_PADDING + (contentHeight - mainBoxHeight) / 2;
const labelStartX = SCREEN_CANVAS_PADDING + SCREEN_BOX_WIDTH + 28;
const targetX = labelStartX + SCREEN_TRANSITION_LANE_WIDTH;
const width = SCREEN_CANVAS_PADDING * 2 + SCREEN_BOX_WIDTH + (targetGroups.length > 0 ? 28 + SCREEN_TRANSITION_LANE_WIDTH + SCREEN_TARGET_BOX_WIDTH : 0);
const height = SCREEN_CANVAS_PADDING * 2 + contentHeight;
const targets = [];
let nextTargetY = SCREEN_CANVAS_PADDING + (contentHeight - targetStackHeight) / 2;
targetGroups.forEach((target, index) => {
const groupHeight = targetHeights[index] ?? SCREEN_TARGET_BOX_MIN_HEIGHT;
const targetBoxHeight = measureScreenPreviewTargetBoxHeight(target);
const targetBoxY = nextTargetY + (groupHeight - targetBoxHeight) / 2;
const labelsHeight = target.actions.length * SCREEN_LABEL_PILL_HEIGHT + Math.max(0, target.actions.length - 1) * SCREEN_LABEL_PILL_GAP;
const labelStartY = nextTargetY + (groupHeight - labelsHeight) / 2;
const labelPills = target.actions.map((action, actionIndex) => ({
action,
x: labelStartX,
y: labelStartY + actionIndex * (SCREEN_LABEL_PILL_HEIGHT + SCREEN_LABEL_PILL_GAP),
width: SCREEN_LABEL_PILL_WIDTH,
height: SCREEN_LABEL_PILL_HEIGHT
}));
targets.push({
target,
x: targetX,
y: targetBoxY,
width: SCREEN_TARGET_BOX_WIDTH,
height: targetBoxHeight,
centerY: targetBoxY + targetBoxHeight / 2,
labelPills
});
nextTargetY += groupHeight + SCREEN_TARGET_BOX_GAP;
});
const contentTop = Math.min(
mainBoxTop,
...targets.map((target) => target.y),
...targets.flatMap((target) => target.labelPills.map((pill) => pill.y))
);
const contentBottom = Math.max(
mainBoxTop + mainBoxHeight,
...targets.map((target) => target.y + target.height),
...targets.flatMap(
(target) => target.labelPills.map((pill) => pill.y + pill.height)
)
);
return {
width,
height,
contentTop,
contentBottom,
mainBoxHeight,
mainBoxTop,
targets
};
}
function measureScreenPreviewBlockHeight(block) {
const visibleRows = Math.max(1, block.items.length);
return SCREEN_SECTION_HEADER_HEIGHT + SCREEN_SECTION_PADDING * 2 + visibleRows * SCREEN_FIELD_ROW_HEIGHT;
}
function measureScreenPreviewTargetBoxHeight(target) {
const availableTextUnits = 24;
const titleLines = estimateScreenPreviewLineCount(
truncateScreenPreviewText(target.targetLabel, SCREEN_MAX_SECTION_CHARS),
availableTextUnits
);
const bodyRows = [
target.selfTarget ? "Self transition" : target.unresolved ? "Transition target not resolved" : "Open target screen",
target.actions.length > 1 ? `${target.actions.length} actions` : ""
].filter((row) => row.length > 0);
const bodyLines = bodyRows.reduce(
(sum, row) => sum + estimateScreenPreviewLineCount(row, availableTextUnits),
0
);
const bodyGap = Math.max(0, bodyRows.length - 1) * SCREEN_TARGET_ROW_GAP;
return Math.max(
SCREEN_TARGET_BOX_MIN_HEIGHT,
SCREEN_TARGET_BOX_HEADER_VERTICAL_PADDING + SCREEN_TARGET_KIND_LINE_HEIGHT + titleLines * SCREEN_TARGET_TITLE_LINE_HEIGHT + SCREEN_TARGET_BOX_BODY_VERTICAL_PADDING + bodyLines * SCREEN_TARGET_ROW_LINE_HEIGHT + bodyGap
);
}
function estimateScreenPreviewLineCount(text, availableUnitsPerLine) {
const trimmed = text.trim();
if (!trimmed) {
return 1;
}
return Math.max(1, Math.ceil(measureScreenPreviewTextUnits(trimmed) / availableUnitsPerLine));
}
function measureScreenPreviewTextUnits(text) {
let units = 0;
for (const char of text) {
units += /[\u1100-\u11ff\u2e80-\u9fff\uf900-\ufaff\uff00-\uffef]/u.test(char) ? 1.6 : 1;
}
return units;
}
function createScreenPreviewMainBox(data, height, top, options) {
const box = activeDocument.createElement("div");
box.className = "mdspec-screen-preview-box";
box.addClass("model-weave-screen-preview-card");
box.addClass("model-weave-screen-card");
box.setCssProps({
"--mw-node-x": `${SCREEN_CANVAS_PADDING}px`,
"--mw-node-y": `${top}px`,
"--mw-node-width": `${SCREEN_BOX_WIDTH}px`,
"--mw-node-height": `${height}px`
});
const header = activeDocument.createElement("header");
header.addClass("model-weave-screen-preview-header");
header.addClass("model-weave-screen-card-header");
const kind = activeDocument.createElement("div");
kind.addClass("model-weave-screen-preview-muted");
kind.textContent = "Screen";
const title = activeDocument.createElement("div");
title.addClass("model-weave-screen-preview-title");
title.addClass("model-weave-screen-card-title");
title.textContent = truncateScreenPreviewText(data.title, SCREEN_MAX_TITLE_CHARS);
header.append(kind, title);
box.appendChild(header);
const body = activeDocument.createElement("div");
body.addClass("model-weave-screen-preview-sections");
body.addClass("model-weave-screen-card-body");
const blocks = data.blocks.length > 0 ? data.blocks : [{ label: "Unassigned", items: [] }];
blocks.forEach((block, index) => {
const section = activeDocument.createElement("section");
section.addClass("model-weave-screen-preview-section");
section.addClass("model-weave-screen-card-section");
if (index > 0) {
section.addClass("model-weave-screen-preview-section-bordered");
}
const sectionHeading = activeDocument.createElement("div");
sectionHeading.addClass("model-weave-screen-preview-section-title");
sectionHeading.textContent = truncateScreenPreviewText(block.label, SCREEN_MAX_SECTION_CHARS);
section.appendChild(sectionHeading);
if (block.items.length === 0) {
const empty = activeDocument.createElement("div");
empty.addClass("model-weave-screen-preview-empty");
empty.textContent = "None";
section.appendChild(empty);
} else {
const list = activeDocument.createElement("ul");
list.addClass("model-weave-screen-preview-list");
for (const item of block.items) {
const entry = activeDocument.createElement("li");
entry.textContent = truncateScreenPreviewText(item.label, SCREEN_MAX_FIELD_CHARS);
list.appendChild(entry);
}
section.appendChild(list);
}
body.appendChild(section);
});
box.appendChild(body);
if (data.sourcePath && options?.onOpenLinkedFile) {
box.tabIndex = 0;
box.setAttribute("role", "button");
box.addClass("model-weave-screen-preview-clickable");
box.title = `Open ${data.title}
${data.sourcePath}`;
if (options.app) {
attachGraphElementHoverPreview({
app: options.app,
targetEl: box,
target: {
mermaidId: `current:${data.sourcePath}`,
linktext: data.sourcePath,
sourcePath: data.sourcePath,
label: data.title,
kind: "screen-current",
targetType: "screen",
filePath: data.sourcePath
},
source: "model-weave"
});
}
const openSource = (openInNewLeaf) => {
options.onOpenLinkedFile?.(data.sourcePath, { openInNewLeaf });
};
box.addEventListener("click", (event) => {
if (event.defaultPrevented) {
return;
}
event.preventDefault();
event.stopPropagation();
openSource(Boolean(event.ctrlKey || event.metaKey));
});
box.addEventListener("auxclick", (event) => {
if (event.button !== 1) {
return;
}
event.preventDefault();
event.stopPropagation();
openSource(true);
});
box.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
event.stopPropagation();
openSource(Boolean(event.ctrlKey || event.metaKey));
}
});
box.addEventListener("pointerdown", (event) => {
event.stopPropagation();
});
}
return box;
}
function createScreenPreviewTransitionSvg(scene) {
const svg = activeDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", `${scene.width}`);
svg.setAttribute("height", `${scene.height}`);
svg.setAttribute("viewBox", `0 0 ${scene.width} ${scene.height}`);
svg.addClass("model-weave-screen-preview-overlay");
const defs = activeDocument.createElementNS("http://www.w3.org/2000/svg", "defs");
const marker = activeDocument.createElementNS("http://www.w3.org/2000/svg", "marker");
marker.setAttribute("id", "mdspec-screen-preview-arrow");
marker.setAttribute("markerWidth", "10");
marker.setAttribute("markerHeight", "10");
marker.setAttribute("refX", "8");
marker.setAttribute("refY", "5");
marker.setAttribute("orient", "auto");
marker.setAttribute("markerUnits", "userSpaceOnUse");
const markerPath = activeDocument.createElementNS("http://www.w3.org/2000/svg", "path");
markerPath.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");
markerPath.setAttribute("fill", SCREEN_ARROW_COLOR);
marker.appendChild(markerPath);
defs.appendChild(marker);
svg.appendChild(defs);
const sourceX = SCREEN_CANVAS_PADDING + SCREEN_BOX_WIDTH;
const sourceY = scene.mainBoxTop + scene.mainBoxHeight / 2;
for (const target of scene.targets) {
const line = activeDocument.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", `${sourceX}`);
line.setAttribute("y1", `${sourceY}`);
line.setAttribute("x2", `${target.x}`);
line.setAttribute("y2", `${target.centerY}`);
line.setAttribute("stroke", SCREEN_ARROW_COLOR);
line.setAttribute("stroke-width", "2");
line.setAttribute("stroke-linecap", "round");
line.setAttribute("marker-end", "url(#mdspec-screen-preview-arrow)");
svg.appendChild(line);
}
return svg;
}
function createScreenPreviewTargetBox(target, options) {
const box = activeDocument.createElement("div");
box.className = "mdspec-screen-preview-target-box";
box.addClass("model-weave-screen-preview-target-box");
box.addClass("model-weave-screen-card");
if (target.target.unresolved) {
box.addClass("model-weave-screen-preview-target-box-unresolved");
}
box.setCssProps({
"--mw-node-x": `${target.x}px`,
"--mw-node-y": `${target.y}px`,
"--mw-node-width": `${target.width}px`,
"--mw-node-height": `${target.height}px`
});
const header = activeDocument.createElement("header");
header.addClass("model-weave-screen-preview-target-header");
header.addClass("model-weave-screen-card-header");
if (target.target.unresolved) {
header.addClass("model-weave-screen-preview-target-header-unresolved");
}
const kind = activeDocument.createElement("div");
kind.addClass("model-weave-screen-preview-target-kind");
kind.textContent = target.target.unresolved ? "unresolved screen" : "screen";
const title = activeDocument.createElement("div");
title.addClass("model-weave-screen-preview-target-title");
title.addClass("model-weave-screen-card-title");
title.textContent = truncateScreenPreviewText(target.target.targetLabel, SCREEN_MAX_SECTION_CHARS);
if (target.target.targetTitle) {
title.title = target.target.targetTitle;
}
header.append(kind, title);
box.appendChild(header);
const body = activeDocument.createElement("div");
body.addClass("model-weave-screen-preview-target-body");
body.addClass("model-weave-screen-card-body");
if (target.target.selfTarget) {
body.createEl("div", {
text: "Self transition",
cls: "model-weave-screen-preview-row"
});
} else if (target.target.unresolved) {
body.createEl("div", {
text: "Transition target not resolved",
cls: "model-weave-screen-preview-row"
});
} else {
body.createEl("div", {
text: "Open target screen",
cls: "model-weave-screen-preview-row"
});
}
if (target.target.actions.length > 1) {
body.createEl("div", {
text: `${target.target.actions.length} actions`,
cls: "model-weave-screen-preview-row"
});
}
box.appendChild(body);
if (target.target.targetPath && options?.onOpenLinkedFile) {
box.tabIndex = 0;
box.setAttribute("role", "button");
box.addClass("model-weave-screen-preview-clickable");
box.title = target.target.targetTitle || target.target.targetLabel;
if (options.app && target.target.targetLinktext) {
attachGraphElementHoverPreview({
app: options.app,
targetEl: box,
target: {
mermaidId: target.target.key,
linktext: target.target.targetLinktext,
sourcePath: dataSourcePathFromTransition(target.target),
label: target.target.targetLabel,
kind: "screen-transition",
targetType: "screen",
filePath: target.target.targetPath
},
source: "model-weave"
});
}
const openTarget = (openInNewLeaf) => {
options.onOpenLinkedFile?.(target.target.targetPath, { openInNewLeaf });
};
box.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
openTarget(Boolean(event.metaKey || event.ctrlKey));
};
box.onauxclick = (event) => {
if (event.button !== 1) {
return;
}
event.preventDefault();
event.stopPropagation();
openTarget(true);
};
box.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
event.stopPropagation();
openTarget(Boolean(event.ctrlKey || event.metaKey));
}
};
box.addEventListener("pointerdown", (event) => {
event.stopPropagation();
});
}
return box;
}
function dataSourcePathFromTransition(target) {
return target.sourcePath ?? target.targetPath ?? "";
}
function createScreenPreviewActionPill(pill, _onNavigateToLocation) {
const element = activeDocument.createElement("span");
element.className = "model-weave-screen-preview-edge-label";
element.addClass("model-weave-screen-transition-label");
element.setCssProps({
"--mw-node-x": `${pill.x}px`,
"--mw-node-y": `${pill.y}px`,
"--mw-node-width": `${pill.width}px`,
"--mw-node-height": `${pill.height}px`
});
element.textContent = truncateScreenPreviewText(pill.action.label, 18);
if (pill.action.title) {
element.title = pill.action.title;
}
return element;
}
function truncateScreenPreviewText(value, maxChars) {
const normalized = value.trim();
if (normalized.length <= maxChars) {
return normalized;
}
return `${normalized.slice(0, Math.max(0, maxChars - 1))}\u2026`;
}
function renderDiagnostics(container, diagnostics, onOpenDiagnostic, getOpenState, setOpenState, language, getQuickFixActions) {
const notes = diagnostics.filter((diagnostic) => normalizeDiagnosticSeverityForViewer(diagnostic) === "info");
const warnings = diagnostics.filter((diagnostic) => normalizeDiagnosticSeverityForViewer(diagnostic) === "warning");
const errors = diagnostics.filter((diagnostic) => normalizeDiagnosticSeverityForViewer(diagnostic) === "error");
if (notes.length === 0 && warnings.length === 0 && errors.length === 0) {
return;
}
const t = createModelWeaveTranslator(toModelWeaveUiLanguage(language));
renderDiagnosticsPanelSummary(container, errors.length, warnings.length, notes.length, t);
renderDiagnosticsBulkActions(container, errors, warnings, notes, t, language);
if (errors.length > 0) {
renderDiagnosticSection(
container,
"errors",
t("diagnostics.errors"),
errors,
onOpenDiagnostic,
"model-weave-diagnostics-summary-error",
getOpenState,
setOpenState,
language,
getQuickFixActions
);
}
if (warnings.length > 0) {
renderDiagnosticSection(
container,
"warnings",
t("diagnostics.warnings"),
warnings,
onOpenDiagnostic,
"model-weave-diagnostics-summary-warning",
getOpenState,
setOpenState,
language,
getQuickFixActions
);
}
if (notes.length > 0) {
renderDiagnosticSection(
container,
"notes",
t("diagnostics.notes"),
notes,
onOpenDiagnostic,
"model-weave-diagnostics-summary-note",
getOpenState,
setOpenState,
language,
getQuickFixActions
);
}
}
function renderDiagnosticsBulkActions(container, errors, warnings, notes, t, language) {
const diagnostics = [...errors, ...warnings, ...notes];
if (diagnostics.length === 0) {
return;
}
const actionBar = container.createDiv({ cls: "model-weave-diagnostics-bulk-actions" });
renderDiagnosticsBulkCopyButton(
actionBar,
t("diagnostics.copyAll"),
formatDiagnosticsBulkMarkdown(
{ errors, warnings, notes },
[
{ title: t("diagnostics.errors"), diagnostics: errors },
{ title: t("diagnostics.warnings"), diagnostics: warnings },
{ title: t("diagnostics.notes"), diagnostics: notes }
],
t,
language
),
true
);
if (errors.length > 0) {
renderDiagnosticsBulkCopyButton(
actionBar,
t("diagnostics.copyErrors"),
formatDiagnosticsBulkMarkdown(
{ errors, warnings, notes },
[{ title: t("diagnostics.errors"), diagnostics: errors }],
t,
language
)
);
}
if (warnings.length > 0) {
renderDiagnosticsBulkCopyButton(
actionBar,
t("diagnostics.copyWarnings"),
formatDiagnosticsBulkMarkdown(
{ errors, warnings, notes },
[{ title: t("diagnostics.warnings"), diagnostics: warnings }],
t,
language
)
);
}
if (notes.length > 0) {
renderDiagnosticsBulkCopyButton(
actionBar,
t("diagnostics.copyNotes"),
formatDiagnosticsBulkMarkdown(
{ errors, warnings, notes },
[{ title: t("diagnostics.notes"), diagnostics: notes }],
t,
language
)
);
}
}
function renderDiagnosticsBulkCopyButton(container, label, markdown, primary = false) {
const button = container.createEl("button", {
text: label,
cls: primary ? "mod-cta model-weave-diagnostics-bulk-action" : "model-weave-secondary-button model-weave-diagnostics-bulk-action"
});
button.type = "button";
button.addEventListener("click", (event) => {
event.preventDefault();
void navigator.clipboard?.writeText(markdown);
});
}
function renderDiagnosticsPanelSummary(container, errorCount, warningCount, noteCount, t) {
const summary = container.createDiv({ cls: "model-weave-diagnostics-panel-summary" });
summary.createSpan({ text: t("diagnostics.summary"), cls: "model-weave-diagnostics-panel-title" });
renderDiagnosticCountChip(summary, t("diagnostics.errors"), errorCount, "error");
renderDiagnosticCountChip(summary, t("diagnostics.warnings"), warningCount, "warning");
renderDiagnosticCountChip(summary, t("diagnostics.notes"), noteCount, "info");
}
function renderDiagnosticCountChip(container, label, count, severity) {
const chip = container.createSpan({
text: label + " " + String(count),
cls: "model-weave-diagnostics-count-chip model-weave-diagnostics-count-" + severity
});
chip.setAttribute("aria-label", label + ": " + String(count));
}
function renderDiagnosticSection(container, key, title, diagnostics, onOpenDiagnostic, summaryModifierClass, getOpenState, setOpenState, language, getQuickFixActions) {
const t = createModelWeaveTranslator(toModelWeaveUiLanguage(language));
const details = container.createEl("details");
details.className = "mdspec-diagnostic-section";
details.addClass("model-weave-preview-section");
details.open = getOpenState ? getOpenState(key, key !== "notes") : key !== "notes";
if (setOpenState) {
details.addEventListener("toggle", () => {
setOpenState(key, details.open);
});
}
details.addClass("model-weave-diagnostics-details");
const summary = details.createEl("summary", {
text: title + " (" + String(diagnostics.length) + ")"
});
summary.addClass("model-weave-diagnostics-summary");
summary.addClass("model-weave-preview-section-title");
summary.addClass(summaryModifierClass);
const list = details.createDiv({ cls: "model-weave-diagnostics-card-list" });
for (const diagnostic of diagnostics) {
renderDiagnosticCard(list, diagnostic, onOpenDiagnostic, t, language, getQuickFixActions);
}
}
function renderDiagnosticCard(container, diagnostic, onOpenDiagnostic, t, language, getQuickFixActions) {
const card = container.createDiv({
cls: "model-weave-diagnostic-card model-weave-diagnostic-card-" + normalizeDiagnosticSeverityForViewer(diagnostic)
});
const severity = normalizeDiagnosticSeverityForViewer(diagnostic);
const header = card.createDiv({ cls: "model-weave-diagnostic-card-header" });
header.createSpan({
text: getDiagnosticSeverityLabel(severity, t),
cls: "model-weave-diagnostic-severity model-weave-diagnostic-severity-" + severity
});
header.createSpan({ text: diagnostic.code, cls: "model-weave-diagnostic-code" });
const message = localizeDiagnosticMessage(diagnostic.message, language);
card.createDiv({ text: message, cls: "model-weave-diagnostic-message" });
const metadata = getDiagnosticMetadata(diagnostic, t);
if (metadata.length > 0) {
const metaList = card.createDiv({ cls: "model-weave-diagnostic-meta-list" });
for (const entry of metadata) {
const item = metaList.createDiv({ cls: "model-weave-diagnostic-meta" });
item.createSpan({ text: entry.label, cls: "model-weave-diagnostic-meta-label" });
item.createSpan({ text: entry.value, cls: "model-weave-diagnostic-meta-value" });
}
}
const details = getDiagnosticDetailEntries(diagnostic, t);
if (details.length > 0) {
const detailBox = card.createEl("details", {
cls: "model-weave-diagnostic-detail-box"
});
detailBox.open = severity === "error";
detailBox.createEl("summary", {
text: t("diagnostics.details.title"),
cls: "model-weave-diagnostic-detail-title"
});
const detailList = detailBox.createDiv({ cls: "model-weave-diagnostic-meta-list" });
for (const entry of details) {
const item = detailList.createDiv({ cls: "model-weave-diagnostic-meta" });
item.createSpan({ text: entry.label, cls: "model-weave-diagnostic-meta-label" });
item.createSpan({ text: entry.value, cls: "model-weave-diagnostic-meta-value" });
}
}
renderDiagnosticActions(
card,
getDiagnosticActionCandidates(diagnostic, message, t, onOpenDiagnostic, getQuickFixActions),
diagnostic
);
}
function getDiagnosticActionCandidates(diagnostic, message, t, onOpenDiagnostic, getQuickFixActions) {
const actions = [];
if (onOpenDiagnostic && hasDiagnosticLocation(diagnostic)) {
actions.push({
id: "open-location",
label: t("diagnostics.openLocation"),
kind: "open",
enabled: true,
reason: t("diagnostics.openLocationTooltip"),
run: onOpenDiagnostic,
primary: true
});
}
actions.push({
id: "copy-message",
label: t("diagnostics.copyMessage"),
kind: "copy",
enabled: true,
copyText: message
});
actions.push({
id: "copy-markdown",
label: t("diagnostics.copyMarkdown"),
kind: "copy",
enabled: true,
copyText: formatDiagnosticAsMarkdown(diagnostic, message, t)
});
const reference = getDiagnosticReferenceValue(diagnostic);
if (reference) {
actions.push({
id: "copy-reference",
label: t("diagnostics.copyReference"),
kind: "copy",
enabled: true,
copyText: reference,
futureFixType: "reference"
});
}
const expectedHeader = getExpectedHeaderForDiagnostic2(diagnostic);
if (expectedHeader) {
actions.push({
id: "copy-expected-header",
label: t("diagnostics.copyExpectedHeader"),
kind: "copy",
enabled: true,
copyText: expectedHeader,
futureFixType: "table-header"
});
}
const frontmatterExample = getFrontmatterExampleForDiagnostic(diagnostic);
if (frontmatterExample) {
actions.push({
id: "copy-frontmatter-example",
label: t("diagnostics.copyFrontmatterExample"),
kind: "copy",
enabled: true,
copyText: frontmatterExample,
futureFixType: "frontmatter"
});
}
actions.push(...getQuickFixActions?.(diagnostic, t) ?? []);
return actions;
}
function renderDiagnosticActions(card, actions, diagnostic) {
if (actions.length === 0) {
return;
}
const actionBar = card.createDiv({ cls: "model-weave-diagnostic-actions" });
const primaryActions = actions.filter((action) => action.primary && action.enabled);
const quickFixActions = actions.filter((action) => action.kind === "quick-fix" && action.enabled);
const copyActions = actions.filter((action) => action.kind === "copy" && action.enabled);
if (primaryActions.length > 0) {
const group = actionBar.createDiv({ cls: "model-weave-diagnostic-action-group model-weave-diagnostic-action-group-primary" });
for (const action of primaryActions) {
renderDiagnosticActionButton(group, action, diagnostic);
}
}
if (quickFixActions.length > 0) {
const group = actionBar.createDiv({ cls: "model-weave-diagnostic-action-group model-weave-diagnostic-action-group-quick-fix" });
group.createSpan({ text: quickFixActions[0]?.groupLabel ?? "Quick fix", cls: "model-weave-diagnostic-action-group-label" });
for (const action of quickFixActions) {
renderDiagnosticActionButton(group, action, diagnostic);
}
}
if (copyActions.length > 0) {
const group = actionBar.createDiv({ cls: "model-weave-diagnostic-action-group" });
for (const action of copyActions) {
renderDiagnosticActionButton(group, action, diagnostic);
}
}
}
function renderDiagnosticActionButton(container, action, diagnostic) {
const button = container.createEl("button", {
text: action.label,
cls: action.primary ? "mod-cta model-weave-diagnostic-action model-weave-diagnostic-action-primary" : "model-weave-secondary-button model-weave-diagnostic-action"
});
button.type = "button";
button.disabled = !action.enabled;
if (action.reason) {
button.title = action.reason;
}
button.dataset.modelWeaveDiagnosticAction = action.id;
if (action.futureFixType) {
button.dataset.modelWeaveFutureFixType = action.futureFixType;
}
button.addEventListener("click", (event) => {
event.preventDefault();
if (!action.enabled) {
return;
}
if (action.kind === "copy" && action.copyText !== void 0) {
void navigator.clipboard?.writeText(action.copyText);
return;
}
void action.run?.(diagnostic);
});
}
function hasDiagnosticLocation(diagnostic) {
return Boolean(
diagnostic.filePath ?? diagnostic.path ?? diagnostic.section ?? diagnostic.field ?? diagnostic.context?.section ?? diagnostic.context?.rowIndex ?? diagnostic.line ?? diagnostic.fromLine ?? diagnostic.toLine
);
}
function normalizeDiagnosticSeverityForViewer(diagnostic) {
const severity = String(diagnostic.severity).toLowerCase();
if (severity === "error") {
return "error";
}
if (severity === "warning" || severity === "warn") {
return "warning";
}
return "info";
}
function getDiagnosticSeverityLabel(severity, t) {
if (severity === "error") {
return t("diagnostics.severity.error");
}
if (severity === "warning") {
return t("diagnostics.severity.warning");
}
return t("diagnostics.severity.note");
}
function getDiagnosticDetailEntries(diagnostic, t) {
const details = [];
const expectedHeader = getExpectedHeaderForDiagnostic2(diagnostic);
if (expectedHeader) {
details.push({ label: t("diagnostics.details.expectedHeader"), value: expectedHeader });
}
const actualHeader = findDiagnosticContextValue(diagnostic.context, [
"actualHeader",
"actualHeaders",
"header",
"headers"
]);
if (actualHeader) {
details.push({ label: t("diagnostics.details.actualHeader"), value: actualHeader });
}
const missingColumns = findDiagnosticContextValue(diagnostic.context, ["missingColumns", "missing"]);
if (missingColumns) {
details.push({ label: t("diagnostics.details.missingColumns"), value: missingColumns });
}
const extraColumns = findDiagnosticContextValue(diagnostic.context, ["extraColumns", "extra"]);
if (extraColumns) {
details.push({ label: t("diagnostics.details.extraColumns"), value: extraColumns });
}
const reference = getDiagnosticReferenceValue(diagnostic);
if (reference) {
const referenceLabel = diagnostic.code === "duplicate-mapping-target-member" ? t("diagnostics.details.duplicateTarget") : diagnostic.code === "unresolved-reference" ? t("diagnostics.details.unresolvedReference") : t("diagnostics.meta.reference");
details.push({ label: referenceLabel, value: reference });
}
const duplicateMappingRow = getDuplicateMappingRowValue(diagnostic);
if (duplicateMappingRow) {
details.push({ label: t("diagnostics.details.mappingRow"), value: duplicateMappingRow });
}
const missingFrontmatterKey = getMissingFrontmatterKey(diagnostic);
if (missingFrontmatterKey) {
details.push({ label: t("diagnostics.details.frontmatterKey"), value: missingFrontmatterKey });
}
const frontmatterExample = getFrontmatterExampleForDiagnostic(diagnostic);
if (frontmatterExample) {
details.push({ label: t("diagnostics.details.frontmatterExample"), value: frontmatterExample });
}
const requestedRenderMode = getRequestedRenderMode(diagnostic);
if (requestedRenderMode) {
details.push({ label: t("diagnostics.details.requestedRenderMode"), value: requestedRenderMode });
details.push({
label: t("diagnostics.details.effectiveRenderMode"),
value: t("diagnostics.details.formatDefault")
});
}
if (diagnostic.code === "class-relation-target-not-diagram-compatible") {
details.push({
label: t("diagnostics.details.diagramCompatibility"),
value: t("diagnostics.details.notClassDiagramCompatible")
});
const targetType = findDiagnosticContextValue(diagnostic.context, [
"targetType",
"resolvedType",
"fileType",
"modelType"
]);
if (targetType) {
details.push({ label: t("diagnostics.details.targetType"), value: targetType });
}
}
const section = getDiagnosticSectionName3(diagnostic);
if (section && /row|reference|mapping|frontmatter|render_mode|class relation/i.test(diagnostic.message)) {
details.push({ label: t("diagnostics.meta.section"), value: section });
}
const row = getDiagnosticStringValue2(diagnostic.context?.rowIndex);
if (row && /row|reference|mapping/i.test(diagnostic.message)) {
details.push({ label: t("diagnostics.meta.row"), value: row });
}
const field = getDiagnosticStringValue2(diagnostic.context?.field) ?? diagnostic.field;
if (field && /reference|field/i.test(diagnostic.message)) {
details.push({ label: t("diagnostics.meta.field"), value: field });
}
details.push(...getDiagnosticGuidanceEntries(diagnostic, t));
return dedupeDiagnosticDetailEntries(details);
}
function getDiagnosticGuidanceEntries(diagnostic, t) {
const guidance = [];
const message = diagnostic.message;
if (isFrontmatterDiagnostic(diagnostic)) {
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t("diagnostics.guidance.frontmatter.what") },
{ label: t("diagnostics.guidance.likelyCause"), value: t("diagnostics.guidance.frontmatter.cause") },
{ label: t("diagnostics.guidance.manualFix"), value: t("diagnostics.guidance.frontmatter.fix") }
);
}
if (isTableHeaderDiagnostic2(diagnostic)) {
const sectionGuidance = resolveDiagnosticSectionGuidance(diagnostic);
if (sectionGuidance?.supported === false) {
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t("diagnostics.guidance.unsupportedSection.what") },
{ label: t("diagnostics.guidance.manualFix"), value: t("diagnostics.guidance.unsupportedSection.fix") }
);
const unsupportedSectionHint = getUnsupportedSectionGuidanceHint(diagnostic, t);
if (unsupportedSectionHint) {
guidance.push({ label: t("diagnostics.guidance.specificHint"), value: unsupportedSectionHint });
}
} else {
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t("diagnostics.guidance.tableHeader.what") },
{ label: t("diagnostics.guidance.likelyCause"), value: t("diagnostics.guidance.tableHeader.cause") },
{ label: t("diagnostics.guidance.manualFix"), value: t("diagnostics.guidance.tableHeader.fix") }
);
}
}
if (isTableRowDiagnostic(diagnostic)) {
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t("diagnostics.guidance.tableRow.what") },
{ label: t("diagnostics.guidance.likelyCause"), value: t("diagnostics.guidance.tableRow.cause") },
{ label: t("diagnostics.guidance.manualFix"), value: t("diagnostics.guidance.tableRow.fix") }
);
}
if (/render_mode/i.test(message)) {
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t("diagnostics.guidance.renderMode.what") },
{ label: t("diagnostics.guidance.likelyCause"), value: t("diagnostics.guidance.renderMode.cause") },
{ label: t("diagnostics.guidance.manualFix"), value: t("diagnostics.guidance.renderMode.fix") }
);
}
if (isFrontmatterWikilinkDiagnostic(diagnostic)) {
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t("diagnostics.guidance.frontmatterWikilink.what") },
{ label: t("diagnostics.guidance.manualFix"), value: t("diagnostics.guidance.frontmatterWikilink.fix") },
{ label: t("diagnostics.guidance.safeExample"), value: 'source: "[[DATA-EXAMPLE]]"' }
);
}
if (diagnostic.code === "unresolved-reference") {
const guidancePrefix = isFlowDiagramLocalEndpointDiagnostic(diagnostic) ? "diagnostics.guidance.flowEndpoint" : "diagnostics.guidance.reference";
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t(guidancePrefix + ".what") },
{ label: t("diagnostics.guidance.likelyCause"), value: t(guidancePrefix + ".cause") },
{ label: t("diagnostics.guidance.manualFix"), value: t(guidancePrefix + ".fix") }
);
}
if (hasNonRecommendedReferenceSeparator(diagnostic)) {
guidance.push(
{ label: t("diagnostics.guidance.whatWrong"), value: t("diagnostics.guidance.referenceSeparator.what") },
{ label: t("diagnostics.guidance.manualFix"), value: t("diagnostics.guidance.referenceSeparator.fix") },
{ label: t("diagnostics.guidance.safeExample"), value: t("diagnostics.guidance.referenceSeparator.example") }
);
}
return guidance;
}
function isFlowDiagramLocalEndpointDiagnostic(diagnostic) {
return diagnostic.code === "unresolved-reference" && diagnostic.context?.referenceKind === "local-object-id" && (diagnostic.field === "Flows.from" || diagnostic.field === "Flows.to");
}
function isFrontmatterDiagnostic(diagnostic) {
return /required frontmatter/i.test(diagnostic.message) || /frontmatter "id" is missing/i.test(diagnostic.message) || /missing required field/i.test(diagnostic.message) && ["id", "name", "type", "kind"].includes((diagnostic.field ?? "").trim().toLowerCase());
}
function isTableHeaderDiagnostic2(diagnostic) {
return diagnostic.code === "invalid-table-column" || /table columns in section/i.test(diagnostic.message) || /table should use:/i.test(diagnostic.message) || /do not match expected .*headers/i.test(diagnostic.message) || /do not match supported .*headers/i.test(diagnostic.message);
}
function isTableRowDiagnostic(diagnostic) {
return diagnostic.code === "invalid-table-row" || /table row in section/i.test(diagnostic.message) || /row .*missing required values/i.test(diagnostic.message) || /table in section .* is incomplete/i.test(diagnostic.message);
}
function isFrontmatterWikilinkDiagnostic(diagnostic) {
const message = diagnostic.message;
return /frontmatter/i.test(message) && /\[\[.*\]\]/.test(message) && /quote|quoted|yaml/i.test(message);
}
function hasNonRecommendedReferenceSeparator(diagnostic) {
const value = getDiagnosticReferenceValue(diagnostic) ?? diagnostic.message;
const wikilinkCount = (value.match(/\[\[[^\]]+\]\]/g) ?? []).length;
if (wikilinkCount < 2) {
return false;
}
return /,|、|\band\b|\s&\s|\s+\s|\s\+\s/i.test(value) && !/\s\/\s/.test(value);
}
function dedupeDiagnosticDetailEntries(entries) {
const seen = /* @__PURE__ */ new Set();
const result = [];
for (const entry of entries) {
const key = entry.label + "\0" + entry.value;
if (seen.has(key)) {
continue;
}
seen.add(key);
result.push(entry);
}
return result;
}
function getExpectedHeaderForDiagnostic2(diagnostic) {
return getExpectedHeaderForDiagnostic(diagnostic);
}
function getUnsupportedSectionGuidanceHint(diagnostic, t) {
const guidance = resolveDiagnosticSectionGuidance(diagnostic);
if (!guidance || guidance.supported) {
return null;
}
const section = guidance.sectionKind ?? "";
if (guidance.fileType === "rule" && (section === "messages" || section === "message")) {
return t("diagnostics.guidance.unsupportedRuleMessages.fix");
}
if (guidance.fileType === "app-process" && (section === "messages" || section === "message")) {
return t("diagnostics.guidance.unsupportedAppProcessMessages.fix");
}
return null;
}
function getDiagnosticSectionName3(diagnostic) {
const contextSection = getDiagnosticStringValue2(diagnostic.context?.section);
if (contextSection) {
return contextSection;
}
const quoted = diagnostic.message.match(/section "([^"]+)"/i)?.[1];
return quoted ?? diagnostic.section ?? null;
}
function getDuplicateMappingRowValue(diagnostic) {
if (!/duplicate mapping row/i.test(diagnostic.message)) {
return null;
}
return getFirstQuotedDiagnosticValue(diagnostic.message);
}
function getMissingFrontmatterKey(diagnostic) {
const contextKey = getDiagnosticStringValue2(diagnostic.context?.frontmatterKey);
if (contextKey) {
return contextKey;
}
if (/frontmatter "id" is missing/i.test(diagnostic.message)) {
return "id";
}
if (!/required frontmatter/i.test(diagnostic.message)) {
return null;
}
return getFirstQuotedDiagnosticValue(diagnostic.message);
}
function getFrontmatterExampleForDiagnostic(diagnostic) {
const missingKey = getMissingFrontmatterKey(diagnostic);
if (!missingKey) {
return null;
}
const modelType = findDiagnosticContextValue(diagnostic.context, ["type", "modelType", "fileType"]) ?? "model_type";
const modelId = missingKey === "id" ? "MODEL-ID" : findDiagnosticContextValue(diagnostic.context, ["id", "modelId"]) ?? "MODEL-ID";
return ["---", "type: " + modelType, "id: " + modelId, "---"].join("\n");
}
function getRequestedRenderMode(diagnostic) {
if (!/render_mode/i.test(diagnostic.message)) {
return null;
}
return getFirstQuotedDiagnosticValue(diagnostic.message);
}
function getDiagnosticMetadata(diagnostic, t) {
const metadata = [];
const file = diagnostic.filePath ?? diagnostic.path;
if (file) {
metadata.push({ label: t("diagnostics.meta.file"), value: file });
}
const section = getDiagnosticStringValue2(diagnostic.context?.section) ?? diagnostic.section ?? diagnostic.field;
if (section) {
metadata.push({ label: t("diagnostics.meta.section"), value: section });
}
const line = diagnostic.line ?? diagnostic.fromLine;
if (typeof line === "number") {
metadata.push({ label: t("diagnostics.meta.line"), value: String(line) });
}
const row = getDiagnosticStringValue2(diagnostic.context?.rowIndex);
if (row) {
metadata.push({ label: t("diagnostics.meta.row"), value: row });
}
const reference = getDiagnosticReferenceValue(diagnostic);
if (reference) {
metadata.push({ label: t("diagnostics.meta.reference"), value: reference });
}
return metadata;
}
function getDiagnosticReferenceValue(diagnostic) {
const contextReference = findDiagnosticContextValue(diagnostic.context, [
"reference",
"ref",
"target",
"targetRef",
"sourceRef",
"value"
]);
if (contextReference) {
return contextReference;
}
if (diagnostic.code !== "unresolved-reference" && !/reference/i.test(diagnostic.message)) {
return null;
}
return getFirstQuotedDiagnosticValue(diagnostic.message);
}
function findDiagnosticContextValue(context, keys) {
if (!context) {
return null;
}
for (const key of keys) {
const value = getDiagnosticStringValue2(context[key]);
if (value) {
return value;
}
}
return null;
}
function getDiagnosticStringValue2(value) {
if (typeof value === "string") {
const trimmed = value.trim();
return trimmed || null;
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
if (Array.isArray(value)) {
const parts = value.map((entry) => typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean" ? String(entry).trim() : "").filter((entry) => entry.length > 0);
return parts.length > 0 ? parts.join(" | ") : null;
}
return null;
}
function getFirstQuotedDiagnosticValue(message) {
const match = message.match(/"([^"]+)"/);
return match?.[1] ?? null;
}
function formatDiagnosticsBulkMarkdown(counts, groups, t, language) {
const allDiagnostics = [...counts.errors, ...counts.warnings, ...counts.notes];
const lines = ["# Model Weave Diagnostics", ""];
const filePath = getDiagnosticsFilePath(allDiagnostics);
if (filePath) {
lines.push("File: " + filePath, "");
}
lines.push("## Summary", "");
lines.push("- " + t("diagnostics.errors") + ": " + String(counts.errors.length));
lines.push("- " + t("diagnostics.warnings") + ": " + String(counts.warnings.length));
lines.push("- " + t("diagnostics.notes") + ": " + String(counts.notes.length));
for (const group of groups) {
if (group.diagnostics.length === 0) {
continue;
}
lines.push("", "## " + group.title, "");
group.diagnostics.forEach((diagnostic, index) => {
appendDiagnosticMarkdown(lines, diagnostic, index + 1, t, language);
});
}
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
}
function appendDiagnosticMarkdown(lines, diagnostic, index, t, language) {
const message = localizeDiagnosticMessage(diagnostic.message, language);
lines.push("### " + String(index) + ". " + diagnostic.code, "");
const severity = normalizeDiagnosticSeverityForViewer(diagnostic);
appendDiagnosticMarkdownField(lines, t("diagnostics.meta.severity"), getDiagnosticSeverityLabel(severity, t));
appendDiagnosticMarkdownField(lines, t("diagnostics.meta.code"), diagnostic.code);
appendDiagnosticMarkdownField(lines, t("diagnostics.meta.message"), message);
const usedFields = /* @__PURE__ */ new Set();
usedFields.add(t("diagnostics.meta.severity") + "\0" + getDiagnosticSeverityLabel(severity, t));
usedFields.add(t("diagnostics.meta.code") + "\0" + diagnostic.code);
usedFields.add(t("diagnostics.meta.message") + "\0" + message);
for (const entry of getDiagnosticMetadata(diagnostic, t)) {
appendUniqueDiagnosticMarkdownField(lines, usedFields, entry.label, entry.value);
}
const lineRange = getDiagnosticLineRange(diagnostic);
if (lineRange) {
appendUniqueDiagnosticMarkdownField(lines, usedFields, t("diagnostics.meta.line"), lineRange);
}
const field = getDiagnosticStringValue2(diagnostic.context?.field) ?? diagnostic.field;
if (field) {
appendUniqueDiagnosticMarkdownField(lines, usedFields, t("diagnostics.meta.field"), field);
}
const detailLines = [];
for (const entry of getDiagnosticDetailEntries(diagnostic, t)) {
const key = entry.label + "\0" + entry.value;
if (usedFields.has(key)) {
continue;
}
usedFields.add(key);
detailLines.push("- " + entry.label + ": " + entry.value);
}
if (detailLines.length > 0) {
lines.push("", "**" + t("diagnostics.details.title") + ":**", "", ...detailLines);
}
lines.push("");
}
function appendUniqueDiagnosticMarkdownField(lines, usedFields, label, value) {
const key = label + "\0" + value;
if (usedFields.has(key)) {
return;
}
usedFields.add(key);
appendDiagnosticMarkdownField(lines, label, value);
}
function appendDiagnosticMarkdownField(lines, label, value) {
lines.push("**" + label + ":** " + value, "");
}
function getDiagnosticsFilePath(diagnostics) {
for (const diagnostic of diagnostics) {
const filePath = diagnostic.filePath ?? diagnostic.path;
if (filePath) {
return filePath;
}
}
return null;
}
function getDiagnosticLineRange(diagnostic) {
if (typeof diagnostic.fromLine === "number" && typeof diagnostic.toLine === "number") {
return diagnostic.fromLine === diagnostic.toLine ? String(diagnostic.fromLine) : String(diagnostic.fromLine) + "-" + String(diagnostic.toLine);
}
if (typeof diagnostic.line === "number") {
return String(diagnostic.line);
}
if (typeof diagnostic.fromLine === "number") {
return String(diagnostic.fromLine);
}
if (typeof diagnostic.toLine === "number") {
return String(diagnostic.toLine);
}
return null;
}
function formatDiagnosticAsMarkdown(diagnostic, localizedMessage, t) {
const lines = [
"- " + t("diagnostics.meta.severity") + ": " + getDiagnosticSeverityLabel(normalizeDiagnosticSeverityForViewer(diagnostic), t),
"- " + t("diagnostics.meta.code") + ": " + diagnostic.code,
"- " + t("diagnostics.meta.message") + ": " + localizedMessage
];
for (const entry of getDiagnosticMetadata(diagnostic, t)) {
lines.push("- " + entry.label + ": " + entry.value);
}
for (const entry of getDiagnosticDetailEntries(diagnostic, t)) {
lines.push("- " + entry.label + ": " + entry.value);
}
return lines.join("\n");
}
function applyFrontmatterPatch(markdown, values) {
const entries = orderFrontmatterPatchEntries(values);
if (entries.length === 0) {
return null;
}
const eol = detectLineEnding(markdown);
const lines = markdown.split(/\r?\n/);
if (lines[0]?.trim() !== "---") {
return null;
}
const closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
if (closingIndex < 0) {
return null;
}
const existingKeys = /* @__PURE__ */ new Set();
for (let index = 1; index < closingIndex; index += 1) {
const match = lines[index].match(/^\s*([A-Za-z0-9_-]+)\s*:/);
if (match) {
existingKeys.add(match[1]);
}
}
const missingEntries = entries.filter(([key]) => !existingKeys.has(key));
if (missingEntries.length === 0) {
return null;
}
lines.splice(closingIndex, 0, ...missingEntries.map(([key, value]) => `${key}: ${value}`));
return lines.join(eol);
}
function orderFrontmatterPatchEntries(values) {
const order = ["type", "id", "name"];
const entries = [];
for (const key of order) {
const value = values[key]?.trim();
if (value) {
entries.push([key, value]);
}
}
for (const [key, value] of Object.entries(values)) {
if (!order.includes(key) && value.trim()) {
entries.push([key, value.trim()]);
}
}
return entries;
}
function detectLineEnding(markdown) {
return markdown.includes("\r\n") ? "\r\n" : "\n";
}
function getMissingRequiredFieldName(diagnostic) {
const match = diagnostic.message.match(/missing required field "([^"]+)"/i);
return match?.[1] ?? null;
}
function toModelWeaveUiLanguage(language) {
if (language === "en" || language === "ja" || language === "auto") {
return language;
}
return "auto";
}
function asModelRecord(value) {
return typeof value === "object" && value !== null ? value : null;
}
function getStringField(value, key) {
const record = asModelRecord(value);
const field = record?.[key];
return typeof field === "string" && field.trim().length > 0 ? field.trim() : void 0;
}
function getFrontmatterString2(value, key) {
const frontmatter = asModelRecord(asModelRecord(value)?.frontmatter);
const field = frontmatter?.[key];
return typeof field === "string" && field.trim().length > 0 ? field.trim() : void 0;
}
function getModelDisplayName(value) {
return getStringField(value, "name") ?? getStringField(value, "title") ?? getStringField(value, "logicalName") ?? getStringField(value, "physicalName") ?? getFrontmatterString2(value, "name") ?? getFrontmatterString2(value, "title") ?? getModelId3(value);
}
function isFlowDiagramViewSelectorVisible(diagram) {
return diagram.diagram.schema === "flow_diagram";
}
function getModelId3(value) {
return getStringField(value, "id") ?? getFrontmatterString2(value, "id");
}
function getModelType(value) {
return getStringField(value, "fileType") ?? getFrontmatterString2(value, "type") ?? getStringField(value, "schema") ?? getFrontmatterString2(value, "schema");
}
function buildGraphIdentityTitle(value, fallbackName, fallbackType) {
const modelId = getModelId3(value);
const modelType = getModelType(value) ?? fallbackType;
const displayName = getModelDisplayName(value) ?? modelId ?? fallbackName ?? "Model";
const suffixParts = [
modelType,
modelId && modelId !== displayName ? modelId : void 0
].filter((part) => Boolean(part));
return suffixParts.length > 0 ? displayName + " (" + suffixParts.join(" / ") + ")" : displayName;
}
function findSummaryMetadataValue(state, keys) {
const normalizedKeys = new Set(keys.map((key) => key.toLowerCase()));
for (const entry of state.metadata) {
if (normalizedKeys.has(entry.label.toLowerCase())) {
return entry.value;
}
}
return void 0;
}
function buildSummaryGraphTitle(state) {
const summaryType = state.summaryKind === "screen" ? "screen" : state.businessFlow ? "app_process" : void 0;
return buildGraphIdentityTitle(
{
title: state.title,
fileType: summaryType,
path: state.filePath,
id: findSummaryMetadataValue(state, ["id", "model id", "model_id"])
},
state.title,
summaryType
);
}
function buildWeaveMapGraphTitle(t, summary) {
const target = summary.modelId || summary.modelLabel || summary.modelPath;
return t("relationship.weaveMap.title") + " \u2014 " + target;
}
function ensureGraphIdentityTitle(root, title) {
const existingTitle = root.querySelector(
".model-weave-mermaid-title, .model-weave-graph-identity-title"
);
const titleElement = existingTitle ?? root.ownerDocument.createElement("h2");
titleElement.textContent = title;
titleElement.title = title;
titleElement.addClass("model-weave-graph-identity-title");
if (!existingTitle) {
root.insertBefore(titleElement, root.firstChild);
}
}
// src/main.ts
var LEGACY_PREVIEW_VIEW_TYPES = [
"mdspec-object-preview",
"mdspec-relations-preview",
"mdspec-diagram-preview"
];
var UNSUPPORTED_MESSAGE = modelWeaveText(
`This file format is not supported. Supported formats: ${SUPPORTED_MODEL_WEAVE_FORMAT_LIST}`,
`\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5BFE\u5FDC\u5F62\u5F0F: ${SUPPORTED_MODEL_WEAVE_FORMAT_LIST}`
);
var DEPRECATED_ER_RELATION_MESSAGE = modelWeaveText(
"This file format is not supported. Use er_entity with ## Relations instead of the legacy er_relation format.",
"\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u65E7 er_relation \u5F62\u5F0F\u3067\u306F\u306A\u304F\u3001er_entity \u306E ## Relations \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002"
);
var DEPRECATED_DIAGRAM_MESSAGE = modelWeaveText(
"This file format is not supported. Migrate legacy diagram_v1 files to class_diagram or er_diagram.",
"\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u65E7 diagram_v1 \u30D5\u30A1\u30A4\u30EB\u306F class_diagram \u307E\u305F\u306F er_diagram \u306B\u79FB\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
);
var MARKDOWN_ONLY_NOTICE2 = modelWeaveText(
"Template insertion is available only for Markdown files.",
"\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u633F\u5165\u306F Markdown \u30D5\u30A1\u30A4\u30EB\u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002"
);
var NON_EMPTY_FILE_NOTICE = modelWeaveText(
"Current file is not empty. Template insertion is available only for empty files.",
"\u73FE\u5728\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u7A7A\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u633F\u5165\u306F\u7A7A\u306E\u30D5\u30A1\u30A4\u30EB\u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002"
);
var ER_RELATION_TYPE_NOTICE = modelWeaveText(
"ER relation block insertion is available only for er_entity files.",
"ER relation block \u306E\u633F\u5165\u306F er_entity \u30D5\u30A1\u30A4\u30EB\u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002"
);
var MODEL_WEAVE_DEFAULT_ZOOM_OPTIONS = [
"fit",
"100"
];
var MODEL_WEAVE_FONT_SIZE_OPTIONS = [
"small",
"normal",
"large"
];
var MODEL_WEAVE_NODE_DENSITY_OPTIONS = [
"compact",
"normal",
"relaxed"
];
var MODEL_WEAVE_UI_LANGUAGE_OPTIONS = [
"auto",
"en",
"ja"
];
var CLASS_RENDER_MODE_OPTIONS = [
"custom",
"mermaid",
"mermaid-detail"
];
var ER_RENDER_MODE_OPTIONS = [
"custom",
"mermaid",
"mermaid-detail"
];
var DFD_RENDER_MODE_OPTIONS = [
"mermaid"
];
var PROCESS_RENDER_MODE_OPTIONS = [
"custom"
];
var SCREEN_RENDER_MODE_OPTIONS = [
"custom"
];
var BUSINESS_FLOW_DIRECTION_OPTIONS = [
"LR",
"TD"
];
var DOMAIN_VIEW_MODE_OPTIONS = [
"mindmap",
"area",
"tree"
];
var FLOW_DIAGRAM_VIEW_MODE_OPTIONS = [
"detail",
"screen"
];
function isClassRenderModeOption(value) {
return CLASS_RENDER_MODE_OPTIONS.some((candidate) => candidate === value);
}
function isErRenderModeOption(value) {
return ER_RENDER_MODE_OPTIONS.some((candidate) => candidate === value);
}
function isDfdRenderModeOption(value) {
return DFD_RENDER_MODE_OPTIONS.some((candidate) => candidate === value);
}
function isProcessRenderModeOption(value) {
return PROCESS_RENDER_MODE_OPTIONS.some((candidate) => candidate === value);
}
function isBusinessFlowDirectionOption(value) {
return BUSINESS_FLOW_DIRECTION_OPTIONS.some((candidate) => candidate === value);
}
function isFlowDiagramViewModeOption(value) {
return FLOW_DIAGRAM_VIEW_MODE_OPTIONS.some((candidate) => candidate === value);
}
function isScreenRenderModeOption(value) {
return SCREEN_RENDER_MODE_OPTIONS.some((candidate) => candidate === value);
}
function isDomainViewModeOption(value) {
return DOMAIN_VIEW_MODE_OPTIONS.some((candidate) => candidate === value);
}
function isDefaultZoomOption(value) {
return MODEL_WEAVE_DEFAULT_ZOOM_OPTIONS.some((candidate) => candidate === value);
}
function isFontSizeOption(value) {
return MODEL_WEAVE_FONT_SIZE_OPTIONS.some((candidate) => candidate === value);
}
function isNodeDensityOption(value) {
return MODEL_WEAVE_NODE_DENSITY_OPTIONS.some((candidate) => candidate === value);
}
function isUiLanguageOption(value) {
return MODEL_WEAVE_UI_LANGUAGE_OPTIONS.some((candidate) => candidate === value);
}
function getFrontmatterValue(frontmatter, key) {
if (typeof frontmatter !== "object" || frontmatter === null) {
return void 0;
}
return frontmatter[key];
}
var ModelWeavePlugin = class extends import_obsidian8.Plugin {
constructor() {
super(...arguments);
this.index = null;
this.previewLeaf = null;
this.rendererOverridesByLeaf = /* @__PURE__ */ new WeakMap();
this.settings = DEFAULT_MODEL_WEAVE_SETTINGS;
}
async onload() {
this.settings = normalizeModelWeaveSettings(await this.loadData());
this.registerView(
MODELING_PREVIEW_VIEW_TYPE,
(leaf) => new ModelingPreviewView(leaf, this.getViewerPreferences(), {
onOpenPreviewInMainPane: (filePath) => {
void this.openPreviewForPathInPane(filePath, "main");
},
onOpenPreviewInNewPane: (filePath) => {
void this.openPreviewForPathInPane(filePath, "new");
},
onOpenModelFile: (filePath) => {
void this.openReferencedFile(filePath);
}
})
);
this.registerHoverLinkSource("model-weave", {
display: "Model Weave",
defaultMod: false
});
this.addSettingTab(new ModelWeaveSettingTab(this.app, this));
this.addCommand({
id: "rebuild-modeling-index",
name: "Rebuild modeling index",
callback: async () => {
await this.rebuildIndex({ parseMode: "full" });
await this.syncPreviewToActiveFile(false, "rerender");
new import_obsidian8.Notice("Modeling index rebuilt");
}
});
this.addCommand({
id: "open-modeling-preview",
name: "Open modeling preview for active file",
callback: async () => {
await this.openPreviewForActiveFile();
}
});
this.addCommand({
id: "open-modeling-preview-in-main-pane",
name: "Open modeling preview in main pane",
callback: async () => {
await this.openPreviewForCurrentFileInPane("main");
}
});
this.addCommand({
id: "open-modeling-preview-in-new-pane",
name: "Open modeling preview in new pane",
callback: async () => {
await this.openPreviewForCurrentFileInPane("new");
}
});
this.addCommand({
id: "insert-class-template",
name: "Insert class template",
callback: async () => {
await this.insertTemplateIntoActiveFile("class");
}
});
this.addCommand({
id: "insert-class-diagram-template",
name: "Insert class diagram template",
callback: async () => {
await this.insertTemplateIntoActiveFile("classDiagram");
}
});
this.addCommand({
id: "insert-er-entity-template",
name: "Insert entity template",
callback: async () => {
await this.insertTemplateIntoActiveFile("erEntity");
}
});
this.addCommand({
id: "insert-er-diagram-template",
name: "Insert entity diagram template",
callback: async () => {
await this.insertTemplateIntoActiveFile("erDiagram");
}
});
this.addCommand({
id: "insert-dfd-object-template",
name: "Insert data flow object template",
callback: async () => {
await this.insertTemplateIntoActiveFile("dfdObject");
}
});
this.addCommand({
id: "insert-dfd-diagram-template",
name: "Insert data flow diagram template",
callback: async () => {
await this.insertTemplateIntoActiveFile("dfdDiagram");
}
});
this.addCommand({
id: "insert-flow-diagram-template",
name: "Insert flow diagram template",
callback: async () => {
await this.insertTemplateIntoActiveFile("flowDiagram");
}
});
this.addCommand({
id: "insert-data-object-template",
name: "Insert data object template",
callback: async () => {
await this.insertTemplateIntoActiveFile("dataObject");
}
});
this.addCommand({
id: "insert-data-object-file-layout-template",
name: "Insert data object file layout template",
callback: async () => {
await this.insertTemplateIntoActiveFile("dataObjectFileLayout");
}
});
this.addCommand({
id: "insert-app-process-template",
name: "Insert app process template",
callback: async () => {
await this.insertTemplateIntoActiveFile("appProcess");
}
});
this.addCommand({
id: "insert-screen-template",
name: "Insert screen template",
callback: async () => {
await this.insertTemplateIntoActiveFile("screen");
}
});
this.addCommand({
id: "insert-codeset-template",
name: "Insert codeset template",
callback: async () => {
await this.insertTemplateIntoActiveFile("codeSet");
}
});
this.addCommand({
id: "insert-message-template",
name: "Insert message template",
callback: async () => {
await this.insertTemplateIntoActiveFile("message");
}
});
this.addCommand({
id: "insert-rule-template",
name: "Insert rule template",
callback: async () => {
await this.insertTemplateIntoActiveFile("rule");
}
});
this.addCommand({
id: "insert-mapping-template",
name: "Insert mapping template",
callback: async () => {
await this.insertTemplateIntoActiveFile("mapping");
}
});
this.addCommand({
id: "insert-domains-template",
name: "Insert domains template",
callback: async () => {
await this.insertTemplateIntoActiveFile("domains");
}
});
this.addCommand({
id: "insert-domain-diagram-template",
name: "Insert domain diagram template",
callback: async () => {
await this.insertTemplateIntoActiveFile("domainDiagram");
}
});
this.addCommand({
id: "insert-color-scheme-template",
name: "Insert color scheme template",
callback: async () => {
await this.insertTemplateIntoActiveFile("colorScheme");
}
});
this.addCommand({
id: "insert-er-relation-block",
name: "Insert entity relation block",
callback: async () => {
await this.insertErRelationBlock();
}
});
this.addCommand({
id: "complete-current-field",
name: "Complete current field",
callback: async () => {
await this.ensureMemberLookupIndex();
openModelWeaveCompletion(this.app, () => this.index);
}
});
this.addCommand({
id: "export-current-diagram-as-png",
name: "Export current diagram as PNG",
callback: async () => {
await this.exportCurrentDiagramAsPng();
}
});
this.registerEvent(
this.app.workspace.on("file-open", async () => {
await this.syncPreviewToActiveFile(false, "external-file-open");
})
);
this.registerEvent(
this.app.workspace.on("active-leaf-change", async (leaf) => {
if (leaf && this.isPreviewLeaf(leaf)) {
return;
}
await this.syncPreviewToActiveFile(false, "external-file-open");
})
);
this.registerEvent(
this.app.vault.on("modify", async () => {
await this.rebuildIndex();
await this.syncPreviewToActiveFile(false, "rerender");
})
);
this.registerEvent(
this.app.vault.on("create", async () => {
await this.rebuildIndex();
await this.syncPreviewToActiveFile(false, "rerender");
})
);
this.registerEvent(
this.app.vault.on("delete", async () => {
await this.rebuildIndex();
await this.syncPreviewToActiveFile(false, "rerender");
})
);
this.registerEvent(
this.app.vault.on("rename", async () => {
await this.rebuildIndex();
await this.syncPreviewToActiveFile(false, "rerender");
})
);
await this.rebuildIndex();
this.app.workspace.onLayoutReady(() => {
void this.normalizePreviewLeaves().then(
() => this.syncPreviewToActiveFile(true, "initial-open")
);
});
}
onunload() {
if (this.previewLeaf) {
this.previewLeaf.detach();
this.previewLeaf = null;
}
}
async rebuildIndex(options = {}) {
const parseMode = options.parseMode ?? "shallow";
const markdownFiles = this.app.vault.getMarkdownFiles();
const files = parseMode === "full" ? await Promise.all(
markdownFiles.map(async (file) => ({
path: file.path,
content: await this.app.vault.cachedRead(file)
}))
) : markdownFiles.map((file) => ({
path: file.path,
frontmatter: this.getCachedFrontmatter(file)
}));
this.index = buildVaultIndex(files, {
parseMode,
resolveRelations: parseMode === "full",
indexMembers: parseMode === "full",
validate: parseMode === "full"
});
}
getCachedFrontmatter(file) {
const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter;
return frontmatter ? { ...frontmatter } : void 0;
}
async ensureFullModelForFile(file) {
if (!this.index) {
return null;
}
if (this.index.state.fullParsedFilePaths[file.path]) {
return this.index.modelsByFilePath[file.path] ?? null;
}
const content = await this.app.vault.cachedRead(file);
replaceVaultIndexFile(this.index, { path: file.path, content }, "full");
return this.index.modelsByFilePath[file.path] ?? null;
}
async ensureFullParsedFiles(shouldParse) {
if (!this.index) {
return;
}
const candidates = Object.values(this.index.modelsByFilePath).filter(shouldParse).map((model) => model.path);
for (const filePath of candidates) {
if (this.index.state.fullParsedFilePaths[filePath]) {
continue;
}
const file = this.app.vault.getAbstractFileByPath(filePath);
if (file instanceof import_obsidian8.TFile) {
await this.ensureFullModelForFile(file);
}
}
}
async ensureStandaloneDomainsValidationReady() {
if (!this.index) {
return;
}
await this.ensureFullParsedFiles(
(candidate) => candidate.fileType === "domains" || candidate.fileType === "dfd-diagram"
);
ensureVaultValidation(this.index);
}
async ensureRelationLookupIndex() {
if (!this.index || this.index.state.relationLookupsBuilt) {
return;
}
await this.ensureFullParsedFiles((model) => model.fileType === "relations");
if (this.index) {
ensureRelationLookups(this.index);
}
}
async ensureMemberLookupIndex() {
if (!this.index || this.index.state.memberLookupsBuilt) {
return;
}
await this.ensureFullParsedFiles(
(model) => [
"object",
"data-object",
"app-process",
"screen",
"codeset",
"message",
"rule",
"er-entity"
].includes(model.fileType)
);
if (this.index) {
ensureMemberLookups(this.index);
}
}
async ensureImpactIndexReady() {
if (!this.index || !this.settings.enableRelationshipView) {
return;
}
await this.ensureFullParsedFiles(
(model) => [
"object",
"er-entity",
"diagram",
"dfd-object",
"dfd-diagram",
"data-object",
"app-process",
"screen",
"rule",
"codeset",
"message",
"mapping",
"color-scheme"
].includes(model.fileType)
);
if (this.index) {
ensureMemberLookups(this.index);
}
}
buildImpactPreviewProps(model) {
if (!this.settings.enableRelationshipView || !this.index || model.fileType === "markdown" || model.fileType === "relations") {
return {};
}
const impactSummary = buildImpactSummary(model, this.index);
const colorScheme = resolveDefaultColorScheme(
this.index,
this.settings.defaultColorSchemeRef
).colorScheme;
const weaveMapMermaidSource = this.buildWeaveMapMermaidSource(
impactSummary,
colorScheme
);
return {
impactSummary,
weaveMapMermaidSource,
colorScheme,
onCopyImpactSummary: () => {
void this.copyImpactSummary(impactSummary);
},
onOpenImpactModel: (filePath, navigation) => {
void this.openReferencedFile(filePath, Boolean(navigation?.openInNewLeaf));
}
};
}
buildWeaveMapMermaidSource(summary, colorScheme) {
try {
return buildWeaveMapMermaidSource(buildWeaveMapModel(summary), {
colorScheme
});
} catch {
return void 0;
}
}
async copyImpactSummary(summary) {
try {
await navigator.clipboard.writeText(formatImpactSummaryAsMarkdown(summary));
new import_obsidian8.Notice("Relationship summary copied");
} catch {
new import_obsidian8.Notice("Failed to copy relationship summary");
}
}
getSettings() {
return this.settings;
}
getViewerPreferences() {
return {
defaultZoom: this.settings.defaultZoom,
fontSize: this.settings.fontSize,
nodeDensity: this.settings.nodeDensity,
defaultDomainsViewMode: this.settings.defaultDomainsViewMode,
defaultDomainDiagramViewMode: this.settings.defaultDomainDiagramViewMode,
defaultBusinessFlowDirection: this.settings.defaultBusinessFlowDirection,
localSourceRoot: this.settings.localSourceRoot,
defaultFlowDiagramViewMode: this.settings.defaultFlowDiagramViewMode,
uiLanguage: this.settings.uiLanguage,
showMermaidRenderDebug: this.settings.showMermaidRenderDebug
};
}
async updateSettings(partial, options) {
this.settings = normalizeModelWeaveSettings({
...this.settings,
...partial
});
await this.saveData(this.settings);
if (options?.refreshViews === false) {
return;
}
await this.refreshOpenModelWeaveViews();
}
async refreshOpenModelWeaveViews() {
const leaves = this.app.workspace.getLeavesOfType(MODELING_PREVIEW_VIEW_TYPE);
for (const leaf of leaves) {
await leaf.loadIfDeferred();
const view = leaf.view;
if (!(view instanceof ModelingPreviewView)) {
continue;
}
view.applyViewerSettings(this.getViewerPreferences());
const currentFilePath = view.getCurrentFilePath();
if (!currentFilePath) {
view.refreshForSettingsChange();
continue;
}
const target = this.app.vault.getAbstractFileByPath(currentFilePath);
if (target instanceof import_obsidian8.TFile) {
await this.showPreviewForFile(target, leaf, false, "rerender");
} else {
view.refreshForSettingsChange();
}
}
}
async openPreviewForActiveFile() {
if (!this.index) {
await this.rebuildIndex();
}
const file = this.app.workspace.getActiveFile();
if (!file) {
new import_obsidian8.Notice(modelWeaveText("No active Markdown file.", "\u30A2\u30AF\u30C6\u30A3\u30D6\u306A Markdown \u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093\u3002"));
return;
}
await this.showPreviewForFile(file, void 0, true, "external-file-open");
}
async openPreviewForCurrentFileInPane(target) {
if (!this.index) {
await this.rebuildIndex();
}
const file = this.getCurrentPreviewCommandFile();
if (!file) {
new import_obsidian8.Notice(modelWeaveText("No active Model Weave file.", "\u30A2\u30AF\u30C6\u30A3\u30D6\u306A Model Weave \u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093\u3002"));
return;
}
await this.openPreviewForFileInPane(file, target);
}
async openPreviewForPathInPane(filePath, target) {
if (!this.index) {
await this.rebuildIndex();
}
const abstractFile = this.app.vault.getAbstractFileByPath(filePath);
if (!(abstractFile instanceof import_obsidian8.TFile)) {
new import_obsidian8.Notice(modelWeaveText("Preview source file was not found.", "Preview \u306E\u5143\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002"));
return;
}
await this.openPreviewForFileInPane(abstractFile, target);
}
async openPreviewForFileInPane(file, target) {
const leaf = target === "new" ? this.app.workspace.getLeaf(true) : this.app.workspace.getLeaf(false);
await this.showPreviewForFile(file, leaf, true, "initial-open", {
managePreviewLeaf: false
});
this.app.workspace.setActiveLeaf(leaf, { focus: true });
}
getCurrentPreviewCommandFile() {
const activePreviewPath = this.getActivePreviewFilePath();
if (activePreviewPath) {
const previewFile = this.app.vault.getAbstractFileByPath(activePreviewPath);
if (previewFile instanceof import_obsidian8.TFile) {
return previewFile;
}
}
const activeFile = this.app.workspace.getActiveFile();
if (activeFile) {
return activeFile;
}
const managedPreviewPath = this.getManagedPreviewFilePath();
if (managedPreviewPath) {
const previewFile = this.app.vault.getAbstractFileByPath(managedPreviewPath);
if (previewFile instanceof import_obsidian8.TFile) {
return previewFile;
}
}
return null;
}
getActivePreviewFilePath() {
const mostRecentLeaf = this.app.workspace.getMostRecentLeaf();
if (!mostRecentLeaf || !this.isPreviewLeaf(mostRecentLeaf)) {
return null;
}
const view = mostRecentLeaf.view;
return view instanceof ModelingPreviewView ? view.getCurrentFilePath() : null;
}
getManagedPreviewFilePath() {
if (!this.previewLeaf || !this.isPreviewLeaf(this.previewLeaf)) {
return null;
}
const view = this.previewLeaf.view;
return view instanceof ModelingPreviewView ? view.getCurrentFilePath() : null;
}
async exportCurrentDiagramAsPng() {
const view = await this.findExportableModelWeaveView();
if (!view) {
new import_obsidian8.Notice(modelWeaveText(
"No exportable diagram is currently displayed.",
"\u73FE\u5728\u3001\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3067\u304D\u308B diagram \u306F\u8868\u793A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002"
));
return;
}
try {
const exportPath = await view.exportCurrentDiagramAsPng();
if (!exportPath) {
new import_obsidian8.Notice("The current view is not ready for export.");
return;
}
new import_obsidian8.Notice(`Diagram exported: ${exportPath}`);
} catch (error) {
if (error instanceof DiagramExportError) {
if (error.code === "bounds-invalid") {
new import_obsidian8.Notice("The current diagram has no measurable export bounds.");
return;
}
new import_obsidian8.Notice("Failed to export the current diagram as PNG.");
return;
}
new import_obsidian8.Notice("Failed to export the current diagram as PNG.");
}
}
async insertTemplateIntoActiveFile(templateKey) {
const target = await this.getActiveMarkdownTarget();
if (!target) {
new import_obsidian8.Notice(MARKDOWN_ONLY_NOTICE2);
return;
}
const currentContent = target.getContent();
if (currentContent.trim().length > 0) {
new import_obsidian8.Notice(NON_EMPTY_FILE_NOTICE);
return;
}
await target.setContent(MODEL_WEAVE_TEMPLATES[templateKey]);
}
async insertErRelationBlock() {
const target = await this.getActiveMarkdownTarget();
if (!target) {
new import_obsidian8.Notice(MARKDOWN_ONLY_NOTICE2);
return;
}
if (this.getActiveFileType(target.file) !== "er_entity") {
new import_obsidian8.Notice(ER_RELATION_TYPE_NOTICE);
return;
}
const lineEnding = this.detectLineEnding(target.getContent());
const block = MODEL_WEAVE_RELATION_TEMPLATES.erRelationBlock.join(lineEnding);
const nextContent = this.appendErRelationBlock(target.getContent(), block, lineEnding);
await target.setContent(nextContent);
}
async getActiveMarkdownTarget() {
const file = this.app.workspace.getActiveFile();
if (!file || file.extension !== "md") {
return null;
}
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian8.MarkdownView);
if (activeView?.file?.path === file.path) {
return {
file,
getContent: () => activeView.editor.getValue(),
setContent: async (content) => {
activeView.editor.setValue(content);
await this.app.vault.modify(file, content);
}
};
}
const cachedContent = await this.app.vault.cachedRead(file);
return {
file,
getContent: () => cachedContent,
setContent: async (content) => {
await this.app.vault.modify(file, content);
}
};
}
getActiveFileType(file) {
const frontmatterType = getFrontmatterValue(
this.app.metadataCache.getFileCache(file)?.frontmatter,
"type"
);
if (typeof frontmatterType === "string" && frontmatterType.trim()) {
return frontmatterType.trim();
}
return void 0;
}
detectLineEnding(content) {
return content.includes("\r\n") ? "\r\n" : "\n";
}
appendErRelationBlock(content, block, lineEnding) {
const section = this.findSection(content, "Relations");
if (section) {
const after = content.slice(section.end);
const sectionText = content.slice(section.start, section.end).replace(/\s*$/u, "");
const updatedSection = `${sectionText}${lineEnding}${lineEnding}${block}${lineEnding}`;
return `${content.slice(0, section.start)}${updatedSection}${after.replace(/^\s*/u, "")}`;
}
const relationsSection = `## Relations${lineEnding}${lineEnding}${block}${lineEnding}`;
return this.insertSectionBeforeNotesOrEnd(content, relationsSection, lineEnding);
}
insertSectionBeforeNotesOrEnd(content, sectionContent, lineEnding) {
const notesSection = this.findSection(content, "Notes");
const trimmedSection = sectionContent.replace(/\s*$/u, "");
if (notesSection) {
const before = content.slice(0, notesSection.start).replace(/\s*$/u, "");
const after = content.slice(notesSection.start).replace(/^\s*/u, "");
return `${before}${lineEnding}${lineEnding}${trimmedSection}${lineEnding}${lineEnding}${after}`;
}
const trimmedContent = content.replace(/\s*$/u, "");
if (!trimmedContent) {
return `${trimmedSection}${lineEnding}`;
}
return `${trimmedContent}${lineEnding}${lineEnding}${trimmedSection}${lineEnding}`;
}
findSection(content, sectionName) {
const headingRegex = new RegExp(`^##\\s+${sectionName}\\s*$`, "m");
const headingMatch = headingRegex.exec(content);
if (!headingMatch || headingMatch.index === void 0) {
return null;
}
const start = headingMatch.index;
const searchStart = start + headingMatch[0].length;
const remainder = content.slice(searchStart);
const nextHeadingMatch = /^##\s+/m.exec(remainder);
const end = nextHeadingMatch && nextHeadingMatch.index !== void 0 ? searchStart + nextHeadingMatch.index : content.length;
return { start, end };
}
async syncPreviewToActiveFile(openIfSupported = false, reason = "rerender") {
const file = this.app.workspace.getActiveFile();
const previewLeaf = this.getManagedPreviewLeaf();
if (!file) {
if (previewLeaf) {
await this.updateEmptyState(previewLeaf, [], void 0, reason);
}
return;
}
if (!this.index) {
await this.rebuildIndex();
}
const model = this.index?.modelsByFilePath[file.path];
const fileType = model ? detectFileType(model.frontmatter) : "markdown";
const isSupported = isModelWeavePreviewSupportedFileType(fileType);
if (!previewLeaf && !openIfSupported) {
return;
}
if (previewLeaf && reason === "external-file-open") {
await previewLeaf.loadIfDeferred();
const currentView = previewLeaf.view;
if (currentView instanceof ModelingPreviewView && currentView.getCurrentFilePath() === file.path) {
return;
}
}
if (!isSupported) {
if (previewLeaf) {
await this.updateEmptyState(
previewLeaf,
[],
await this.getEmptyStateMessage(file),
reason
);
}
return;
}
await this.showPreviewForFile(
file,
previewLeaf ?? void 0,
openIfSupported,
reason
);
}
async showPreviewForFile(file, preferredLeaf, activate = true, reason = "rerender", options) {
if (!this.index) {
await this.rebuildIndex();
}
if (!this.index) {
return;
}
const model = await this.ensureFullModelForFile(file);
const leaf = await this.ensurePreviewLeaf(
preferredLeaf,
activate,
options?.managePreviewLeaf ?? true
);
await leaf.loadIfDeferred();
const view = leaf.view;
if (!(view instanceof ModelingPreviewView)) {
return;
}
view.applyViewerSettings(this.getViewerPreferences());
if (!model) {
view.updateContent({
mode: "empty",
message: await this.getEmptyStateMessage(file),
warnings: []
}, reason);
return;
}
const fileType = detectFileType(model.frontmatter);
const leafRenderModeOverride = this.getRendererOverrideForLeaf(leaf, file.path);
const renderMode = this.resolveFileRenderMode(
file.path,
fileType,
model.frontmatter,
"kind" in model && typeof model.kind === "string" ? model.kind : null,
leafRenderModeOverride
);
const renderModeWarnings = renderMode.diagnostics;
const rendererSelection = this.buildRendererSelectionState(
file,
leaf,
renderMode,
fileType,
"kind" in model && typeof model.kind === "string" ? model.kind : null
);
await this.ensureImpactIndexReady();
const impactPreviewProps = this.buildImpactPreviewProps(
this.index.modelsByFilePath[file.path] ?? model
);
switch (fileType) {
case "object":
case "er-entity": {
const objectModel = model.fileType === "object" || model.fileType === "er-entity" ? model : null;
if (objectModel?.fileType === "object") {
await this.ensureFullParsedFiles((candidate) => candidate.fileType === "object");
await this.ensureRelationLookupIndex();
} else if (objectModel?.fileType === "er-entity") {
await this.ensureFullParsedFiles((candidate) => candidate.fileType === "er-entity");
}
const context = objectModel && this.index ? resolveObjectContext(objectModel, this.index) : null;
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings,
...context?.warnings ?? []
];
if (renderMode.actualRenderer === "mermaid" && context && !context.relatedObjects.some((entry) => entry.direction === "outgoing")) {
warnings.push({
code: "invalid-structure",
message: "Mermaid overview: no outbound relations to display.",
severity: "info",
filePath: file.path,
section: "Relations"
});
}
if (objectModel) {
const diagnostics = buildCurrentObjectDiagnostics(
objectModel,
this.index,
context,
warnings
);
view.updateContent({
mode: "object",
model: objectModel,
context,
...impactPreviewProps,
warnings: diagnostics,
rendererSelection,
onOpenDiagnostic: (diagnostic) => {
void this.openDiagnosticLocation(file.path, diagnostic);
},
onOpenObject: (objectId, navigation) => {
void this.openObjectNote(objectId, file.path, navigation);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "dfd-object": {
const dfdObject = model.fileType === "dfd-object" ? model : null;
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (dfdObject) {
const diagnostics = buildCurrentObjectDiagnostics(
dfdObject,
this.index,
null,
warnings
);
const diagram = buildDfdObjectScene(dfdObject);
view.updateContent({
mode: "dfd-object",
model: dfdObject,
diagram,
...impactPreviewProps,
warnings: [...diagnostics, ...diagram.warnings],
rendererSelection,
onOpenDiagnostic: (diagnostic) => {
void this.openDiagnosticLocation(file.path, diagnostic);
},
onOpenObject: (objectId, navigation) => {
void this.openObjectNote(objectId, file.path, navigation);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "diagram": {
if (model.fileType === "diagram") {
if (model.kind === "er") {
await this.ensureFullParsedFiles((candidate) => candidate.fileType === "er-entity");
} else {
await this.ensureFullParsedFiles((candidate) => candidate.fileType === "object");
await this.ensureRelationLookupIndex();
}
}
const resolved = model.fileType === "diagram" && this.index ? resolveDiagramRelations(model, this.index) : null;
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings,
...resolved?.warnings ?? []
];
const diagnostics = resolved ? buildCurrentDiagramDiagnostics(resolved, warnings) : warnings;
view.updateContent(
resolved ? {
mode: "diagram",
diagram: resolved,
...impactPreviewProps,
warnings: diagnostics,
rendererSelection,
onOpenDiagnostic: (diagnostic) => {
void this.openDiagnosticLocation(file.path, diagnostic);
},
onOpenObject: (objectId, navigation) => {
void this.openObjectNote(objectId, file.path, navigation);
}
} : {
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
},
reason
);
return;
}
case "dfd-diagram":
case "flow-diagram": {
const dfdLikeDiagramModel = isDfdLikeDiagramPreviewModel(model) ? model : null;
if (dfdLikeDiagramModel) {
await this.ensureFullParsedFiles(
(candidate) => candidate.fileType === "dfd-object" || candidate.fileType === "color-scheme" || candidate.fileType === "domains" || candidate.fileType === "screen" || candidate.fileType === "app-process" || candidate.fileType === "data-object"
);
}
const colorSchemeResult = resolveDefaultColorScheme(
this.index,
this.settings.defaultColorSchemeRef
);
const resolved = dfdLikeDiagramModel && this.index ? resolveDiagramRelations(dfdLikeDiagramModel, this.index) : null;
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings,
...colorSchemeResult.warnings,
...resolved?.warnings ?? []
];
const diagnostics = resolved ? buildCurrentDiagramDiagnostics(resolved, warnings) : warnings;
view.updateContent(
resolved ? {
mode: "diagram",
diagram: resolved,
...impactPreviewProps,
warnings: diagnostics,
colorScheme: colorSchemeResult.colorScheme,
rendererSelection,
onOpenDiagnostic: (diagnostic) => {
void this.openDiagnosticLocation(file.path, diagnostic);
},
onOpenObject: (objectId, navigation) => {
void this.openObjectNote(objectId, file.path, navigation);
}
} : {
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
},
reason
);
return;
}
case "data-object": {
await this.ensureMemberLookupIndex();
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (model.fileType === "data-object") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
view.updateContent({
mode: "summary",
rendererSelection,
...impactPreviewProps,
filePath: model.path,
title: model.name || model.id || this.getPathBasename(model.path),
sourceLinks: model.sourceLinks,
metadata: [
{ label: "type", value: "data_object" },
{ label: "id", value: model.id || "(missing)" },
{ label: "name", value: model.name || "(missing)" },
...model.kind ? [{ label: "kind", value: model.kind }] : [],
...model.dataFormat ? [{ label: "data_format", value: model.dataFormat }] : [],
{ label: "path", value: model.path }
],
sections: this.describeDataObjectSections(model, file.path),
counts: [
{ label: "Format entries", value: model.formatEntries.length },
{ label: "Records", value: model.records.length },
{ label: "Fields", value: model.fields.length }
],
tables: this.buildDataObjectSummaryTables(model, file.path),
warnings: diagnostics,
onNavigateToLocation: (location) => {
void this.openFileLocation(file.path, location.line, location.ch ?? 0);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "app-process": {
await this.ensureMemberLookupIndex();
await this.ensureFullParsedFiles(
(candidate) => candidate.fileType === "color-scheme" || candidate.fileType === "domains"
);
const colorSchemeResult = resolveDefaultColorScheme(
this.index,
this.settings.defaultColorSchemeRef
);
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings,
...colorSchemeResult.warnings
];
if (model.fileType === "app-process") {
const domainPlacement = resolveAppProcessDomainPlacement(
model,
this.index
);
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
[
...warnings,
...this.buildAppProcessBusinessFlowWarnings(model),
...domainPlacement.warnings
]
);
view.updateContent({
mode: "summary",
rendererSelection,
...impactPreviewProps,
filePath: model.path,
title: model.name || model.id || this.getPathBasename(model.path),
sourceLinks: model.sourceLinks,
metadata: [
{ label: "type", value: "app_process" },
{ label: "id", value: model.id || "(missing)" },
{ label: "name", value: model.name || "(missing)" },
...model.kind ? [{ label: "kind", value: model.kind }] : [],
{ label: "path", value: model.path }
],
sections: this.describeAppProcessSections(model, file.path),
counts: [
{ label: "Triggers", value: model.triggers.length },
{ label: "Inputs", value: model.inputs.length },
{ label: "Outputs", value: model.outputs.length },
{ label: "Transitions", value: model.transitions.length },
...model.steps?.length ? [{ label: "Steps", value: model.steps.length }] : [],
...model.hasExplicitFlows ? [{ label: "Flows", value: model.flows?.length ?? 0 }] : [],
...model.domains.length > 0 ? [{ label: "Domains", value: model.domains.length }] : [],
...model.domainSources.length > 0 ? [{ label: "Domain Sources", value: model.domainSources.length }] : []
],
textSections: this.buildAppProcessTextSections(model),
tables: this.buildAppProcessSummaryTables(model, file.path),
appProcessDomainPlacement: domainPlacement,
interactionIndex: this.index,
businessFlowDirection: model.flowDirection,
businessFlow: (model.steps?.length ?? 0) > 0 ? {
title: model.name || model.id,
inputs: model.inputs,
outputs: model.outputs,
steps: model.steps ?? [],
flows: model.flows ?? [],
hasExplicitFlows: Boolean(model.hasExplicitFlows),
domains: domainPlacement.domains.length > 0 ? domainPlacement.domains : model.domains
} : void 0,
colorScheme: colorSchemeResult.colorScheme,
warnings: diagnostics,
onNavigateToLocation: (location) => {
void this.openFileLocation(file.path, location.line, location.ch ?? 0);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "screen": {
await this.ensureMemberLookupIndex();
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (model.fileType === "screen") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
const localProcesses = model.localProcesses.length > 0 ? model.localProcesses.map((process) => ({
label: process.heading,
line: process.line,
ch: 0
})) : this.collectScreenLocalProcesses(file.path);
const invokedProcesses = this.collectScreenInvokedProcesses(model);
const outgoingScreens = this.collectScreenOutgoingScreens(model);
const screenPreviewTransitions = this.buildScreenPreviewTransitions(model);
view.updateContent({
mode: "summary",
summaryKind: "screen",
rendererSelection,
...impactPreviewProps,
filePath: model.path,
title: model.name || model.id || this.getPathBasename(model.path),
sourceLinks: model.sourceLinks,
metadata: [
{ label: "type", value: "screen" },
{ label: "id", value: model.id || "(missing)" },
{ label: "name", value: model.name || "(missing)" },
...model.screenType ? [{ label: "screen_type", value: model.screenType }] : [],
{ label: "path", value: model.path }
],
sections: this.describeScreenSections(model, file.path),
counts: [
{ label: "Layouts", value: model.layouts.length },
{ label: "Fields", value: model.fields.length },
{ label: "Actions", value: model.actions.length },
{ label: "Messages", value: model.messages.length },
{
label: "Local processes",
value: localProcesses.length
},
{ label: "Invoked processes", value: invokedProcesses.length },
{ label: "Outgoing screens", value: outgoingScreens.length }
],
tables: this.buildScreenSummaryTables(model, file.path),
layoutBlocks: this.buildScreenLayoutBlocks(model),
screenPreviewTransitions,
localProcesses,
navigationLists: [
{ title: "Invoked processes", items: invokedProcesses },
{ title: "Transitions / Outgoing screens", items: outgoingScreens }
],
warnings: diagnostics,
onNavigateToLocation: (location) => {
void this.openFileLocation(file.path, location.line, location.ch ?? 0);
},
onOpenLinkedFile: (targetPath, navigation) => {
void this.openReferencedFile(
targetPath,
navigation?.openInNewLeaf ?? false
);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "codeset": {
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (model.fileType === "codeset") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
view.updateContent({
mode: "summary",
rendererSelection,
...impactPreviewProps,
filePath: model.path,
title: model.name || model.id || this.getPathBasename(model.path),
sourceLinks: model.sourceLinks,
metadata: [
{ label: "type", value: "codeset" },
{ label: "id", value: model.id || "(missing)" },
{ label: "name", value: model.name || "(missing)" },
...model.kind ? [{ label: "kind", value: model.kind }] : [],
{ label: "path", value: model.path }
],
sections: this.describeCodeSetSections(model, file.path),
counts: [{ label: "Values", value: model.values.length }],
textSections: [
...model.summary?.trim() ? [{ title: "Summary", lines: [model.summary.trim()] }] : [],
...(model.notes ?? []).length > 0 ? [{ title: "Notes", lines: model.notes ?? [] }] : []
],
tables: this.buildCodeSetSummaryTables(file.path),
warnings: diagnostics,
onNavigateToLocation: (location) => {
void this.openFileLocation(file.path, location.line, location.ch ?? 0);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "message": {
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (model.fileType === "message") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
view.updateContent({
mode: "summary",
rendererSelection,
...impactPreviewProps,
filePath: model.path,
title: model.name || model.id || this.getPathBasename(model.path),
sourceLinks: model.sourceLinks,
metadata: [
{ label: "type", value: "message" },
{ label: "id", value: model.id || "(missing)" },
{ label: "name", value: model.name || "(missing)" },
...model.kind ? [{ label: "kind", value: model.kind }] : [],
{ label: "path", value: model.path }
],
sections: this.describeMessageSections(model, file.path),
counts: [{ label: "Messages", value: model.messages.length }],
textSections: [
...model.summary?.trim() ? [{ title: "Summary", lines: [model.summary.trim()] }] : [],
...(model.notes ?? []).length > 0 ? [{ title: "Notes", lines: model.notes ?? [] }] : []
],
tables: this.buildMessageSummaryTables(file.path),
warnings: diagnostics,
onNavigateToLocation: (location) => {
void this.openFileLocation(file.path, location.line, location.ch ?? 0);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "rule": {
await this.ensureMemberLookupIndex();
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (model.fileType === "rule") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
view.updateContent({
mode: "summary",
rendererSelection,
...impactPreviewProps,
filePath: model.path,
title: model.name || model.id || this.getPathBasename(model.path),
sourceLinks: model.sourceLinks,
metadata: [
{ label: "type", value: "rule" },
{ label: "id", value: model.id || "(missing)" },
{ label: "name", value: model.name || "(missing)" },
...model.kind ? [{ label: "kind", value: model.kind }] : [],
{ label: "path", value: model.path }
],
sections: this.describeRuleSections(model, file.path),
counts: [
{ label: "Inputs", value: model.inputs.length },
{ label: "References", value: model.references.length },
{ label: "Messages", value: model.messages.length }
],
tables: this.buildRuleSummaryTables(model, file.path),
warnings: diagnostics,
onNavigateToLocation: (location) => {
void this.openFileLocation(file.path, location.line, location.ch ?? 0);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "mapping": {
await this.ensureMemberLookupIndex();
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (model.fileType === "mapping") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
view.updateContent({
mode: "summary",
rendererSelection,
...impactPreviewProps,
filePath: model.path,
title: model.name || model.id || this.getPathBasename(model.path),
sourceLinks: model.sourceLinks,
metadata: [
{ label: "type", value: "mapping" },
{ label: "id", value: model.id || "(missing)" },
{ label: "name", value: model.name || "(missing)" },
...model.kind ? [{ label: "kind", value: model.kind }] : [],
...model.source ? [{ label: "source", value: this.formatReferenceDisplay(model.source) }] : [],
...model.target ? [{ label: "target", value: this.formatReferenceDisplay(model.target) }] : [],
{ label: "path", value: model.path }
],
sections: this.describeMappingSections(model, file.path),
counts: [
{ label: "Scope", value: model.scope.length },
{ label: "Mappings", value: model.mappings.length }
],
tables: this.buildMappingSummaryTables(file.path),
warnings: diagnostics,
onNavigateToLocation: (location) => {
void this.openFileLocation(file.path, location.line, location.ch ?? 0);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "color-scheme": {
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings
];
if (model.fileType === "color-scheme") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
view.updateContent({
mode: "color-scheme",
model,
warnings: diagnostics,
rendererSelection,
onOpenDiagnostic: (diagnostic) => {
void this.openDiagnosticLocation(file.path, diagnostic);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "domains": {
await this.ensureStandaloneDomainsValidationReady();
await this.ensureFullParsedFiles((candidate) => candidate.fileType === "color-scheme");
const colorSchemeResult = resolveDefaultColorScheme(
this.index,
this.settings.defaultColorSchemeRef
);
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings,
...colorSchemeResult.warnings
];
if (model.fileType === "domains") {
const diagnostics = buildCurrentObjectDiagnostics(
model,
this.index,
null,
warnings
);
view.updateContent({
mode: "domains",
model,
relationships: buildDomainRelationshipSummaries(model, this.index),
warnings: diagnostics,
colorScheme: colorSchemeResult.colorScheme,
rendererSelection,
onOpenDiagnostic: (diagnostic) => {
void this.openDiagnosticLocation(file.path, diagnostic);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "domain-diagram": {
await this.ensureStandaloneDomainsValidationReady();
await this.ensureFullParsedFiles((candidate) => candidate.fileType === "color-scheme");
const colorSchemeResult = resolveDefaultColorScheme(
this.index,
this.settings.defaultColorSchemeRef
);
const warnings = [
...this.index.warningsByFilePath[file.path] ?? [],
...renderModeWarnings,
...colorSchemeResult.warnings
];
if (model.fileType === "domain-diagram") {
const resolved = resolveDomainDiagram(model, this.index);
const diagnostics = [
...warnings,
...resolved.warnings
];
const mergedDomainsModel = {
...model,
fileType: "domains",
schema: "domains",
domains: resolved.domains,
description: void 0
};
view.updateContent({
mode: "domain-diagram",
resolved,
relationships: buildDomainRelationshipSummaries(
mergedDomainsModel,
this.index
),
warnings: diagnostics,
colorScheme: colorSchemeResult.colorScheme,
rendererSelection,
onOpenDiagnostic: (diagnostic) => {
void this.openDiagnosticLocation(file.path, diagnostic);
}
}, reason);
} else {
view.updateContent({
mode: "empty",
message: UNSUPPORTED_MESSAGE,
warnings: []
}, reason);
}
return;
}
case "markdown":
default:
view.updateContent({
mode: "empty",
message: await this.getEmptyStateMessage(file),
warnings: this.index.warningsByFilePath[file.path] ?? []
}, reason);
}
}
async updateEmptyState(leaf, warnings = [], message = UNSUPPORTED_MESSAGE, reason = "rerender") {
await leaf.loadIfDeferred();
if (leaf.view instanceof ModelingPreviewView) {
leaf.view.updateContent({
mode: "empty",
message,
warnings
}, reason);
}
}
async getEmptyStateMessage(file) {
const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter;
if (frontmatter?.type === "er_relation") {
return DEPRECATED_ER_RELATION_MESSAGE;
}
if (frontmatter?.type === "diagram" || frontmatter?.schema === "diagram_v1" || typeof frontmatter?.diagram_kind === "string") {
return DEPRECATED_DIAGRAM_MESSAGE;
}
const content = await this.app.vault.cachedRead(file);
if (/^\s*---[\s\S]*?\btype\s*:\s*er_relation\b[\s\S]*?---/m.test(content)) {
return DEPRECATED_ER_RELATION_MESSAGE;
}
if (/^\s*---[\s\S]*?\btype\s*:\s*diagram\b[\s\S]*?---/m.test(content) || /^\s*---[\s\S]*?\bschema\s*:\s*diagram_v1\b[\s\S]*?---/m.test(content) || /^\s*---[\s\S]*?\bdiagram_kind\s*:\s*[A-Za-z0-9_-]+\b[\s\S]*?---/m.test(content)) {
return DEPRECATED_DIAGRAM_MESSAGE;
}
return UNSUPPORTED_MESSAGE;
}
describeDataObjectSections(model, filePath) {
const lines = this.getFileLines(filePath);
const sections = [];
const orderedKeys = ["Summary", "Format", "Records", "Fields", "Notes"];
for (const key of orderedKeys) {
if (!(key in model.sections)) {
continue;
}
const line = this.findHeadingLine(lines, key);
if (key === "Format") {
sections.push({ label: `Format: ${model.formatEntries.length} rows`, line, ch: 0 });
} else if (key === "Records") {
sections.push({ label: `Records: ${model.records.length} rows`, line, ch: 0 });
} else if (key === "Fields") {
sections.push({ label: `Fields: ${model.fields.length} rows`, line, ch: 0 });
} else {
sections.push({ label: key, line, ch: 0 });
}
}
return sections;
}
buildDataObjectSummaryTables(model, filePath) {
const formatRows = this.readTableRows(filePath, "Format");
const recordRows = this.readTableRows(filePath, "Records");
const fieldRows = this.readTableRows(filePath, "Fields");
const tables = [];
if (model.formatEntries.length > 0) {
tables.push({
title: "Format summary",
columns: ["key", "value", "notes"],
rows: formatRows.map((row) => ({
cells: [row.record.key ?? "", row.record.value ?? "", row.record.notes ?? ""],
line: row.line,
ch: row.ch
}))
});
}
if (model.records.length > 0) {
tables.push({
title: "Records summary",
columns: ["record_type", "name", "occurrence", "notes"],
rows: recordRows.map((row) => ({
cells: [
row.record.record_type ?? "",
row.record.name ?? "",
row.record.occurrence ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
if (model.fieldMode === "file_layout") {
tables.push({
title: "Fields summary",
columns: ["record_type", "no", "name", "label", "type", "length", "position", "field_format", "ref", "notes"],
rows: fieldRows.map((row) => ({
cells: [
row.record.record_type ?? "",
row.record.no ?? "",
row.record.name ?? "",
row.record.label ?? "",
row.record.type ?? "",
row.record.length ?? "",
row.record.position ?? "",
row.record.field_format ?? "",
this.formatReferenceDisplay(row.record.ref),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
} else {
tables.push({
title: "Fields summary",
columns: ["name", "label", "type", "length", "required", "ref", "notes"],
rows: fieldRows.map((row) => ({
cells: [
row.record.name ?? "",
row.record.label ?? "",
row.record.type ?? "",
row.record.length ?? "",
row.record.required ?? "",
this.formatReferenceDisplay(row.record.ref),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
return tables;
}
getPathBasename(path2) {
const slashNormalized = path2.replace(/\\/g, "/");
const lastSegment = slashNormalized.split("/").pop() ?? slashNormalized;
return lastSegment.replace(/\.md$/i, "");
}
describeScreenSections(model, filePath) {
const lines = this.getFileLines(filePath);
const sections = [];
const orderedKeys = [
"Summary",
"Layout",
"Fields",
"Actions",
"Messages",
"Notes",
"Local Processes",
"Transitions"
];
for (const key of orderedKeys) {
if (!(key in model.sections)) {
continue;
}
const line = this.findHeadingLine(lines, key);
if (key === "Layout") {
sections.push({ label: `Layout: ${model.layouts.length} rows`, line, ch: 0 });
} else if (key === "Fields") {
sections.push({ label: `Fields: ${model.fields.length} rows`, line, ch: 0 });
} else if (key === "Actions") {
sections.push({ label: `Actions: ${model.actions.length} rows`, line, ch: 0 });
} else if (key === "Messages") {
sections.push({ label: `Messages: ${model.messages.length} rows`, line, ch: 0 });
} else if (key === "Local Processes") {
sections.push({
label: model.localProcesses.length > 0 ? `Local Processes: ${model.localProcesses.length} headings` : "Local Processes",
line,
ch: 0
});
} else if (key === "Transitions") {
sections.push({
label: model.legacyTransitions.length > 0 ? `Transitions (legacy): ${model.legacyTransitions.length} rows` : "Transitions (legacy)",
line,
ch: 0
});
} else {
sections.push({ label: key, line, ch: 0 });
}
}
return sections;
}
describeAppProcessSections(model, filePath) {
const lines = this.getFileLines(filePath);
const sections = [];
const orderedKeys = [
"Summary",
"Domains",
"Domain Sources",
"Triggers",
"Inputs",
"Steps",
"Flows",
"Outputs",
"Transitions",
"Errors",
"Notes"
];
for (const key of orderedKeys) {
if (!(key in model.sections)) {
continue;
}
const line = this.findHeadingLine(lines, key);
if (key === "Domains") {
sections.push({
label: `Domains: ${model.sections.Domains?.length ?? 0} lines`,
line,
ch: 0
});
} else if (key === "Domain Sources") {
sections.push({
label: `Domain Sources: ${model.sections["Domain Sources"]?.length ?? 0} lines`,
line,
ch: 0
});
} else if (key === "Inputs") {
sections.push({ label: `Inputs: ${model.inputs.length} rows`, line, ch: 0 });
} else if (key === "Outputs") {
sections.push({ label: `Outputs: ${model.outputs.length} rows`, line, ch: 0 });
} else if (key === "Triggers") {
sections.push({ label: `Triggers: ${model.triggers.length} rows`, line, ch: 0 });
} else if (key === "Transitions") {
sections.push({ label: `Transitions: ${model.transitions.length} rows`, line, ch: 0 });
} else if (key === "Steps") {
sections.push({
label: (model.steps?.length ?? 0) > 0 ? `Steps: ${model.steps?.length ?? 0} rows` : "Steps: prose",
line,
ch: 0
});
} else if (key === "Flows") {
sections.push({ label: `Flows: ${model.flows?.length ?? 0} rows`, line, ch: 0 });
} else {
sections.push({ label: key, line, ch: 0 });
}
}
return sections;
}
buildAppProcessTextSections(model) {
const sections = [];
if ((model.steps?.length ?? 0) === 0 && !this.sectionContainsMarkdownTable(model.sections.Steps)) {
const stepsLines = this.getReadableSectionLines(model.sections.Steps);
if (stepsLines.length > 0) {
sections.push({ title: "Steps", lines: stepsLines });
}
}
return sections;
}
getReadableSectionLines(lines) {
return (lines ?? []).map((line) => line.replace(/\s+$/u, ""));
}
sectionContainsMarkdownTable(lines) {
const tableLines = (lines ?? []).map((line) => line.trim()).filter((line) => line.startsWith("|"));
if (tableLines.length < 2) {
return false;
}
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(tableLines[1]);
}
describeCodeSetSections(model, filePath) {
const lines = this.getFileLines(filePath);
const orderedKeys = ["Summary", "Values", "Notes"];
const sections = [];
for (const key of orderedKeys) {
if (!(key in model.sections)) {
continue;
}
const line = this.findHeadingLine(lines, key);
if (key === "Values") {
sections.push({ label: `Values: ${model.values.length} rows`, line, ch: 0 });
} else {
sections.push({ label: key, line, ch: 0 });
}
}
return sections;
}
describeMessageSections(model, filePath) {
const lines = this.getFileLines(filePath);
const orderedKeys = ["Summary", "Messages", "Notes"];
const sections = [];
for (const key of orderedKeys) {
if (!(key in model.sections)) {
continue;
}
const line = this.findHeadingLine(lines, key);
if (key === "Messages") {
sections.push({ label: `Messages: ${model.messages.length} rows`, line, ch: 0 });
} else {
sections.push({ label: key, line, ch: 0 });
}
}
return sections;
}
describeRuleSections(model, filePath) {
const lines = this.getFileLines(filePath);
const orderedKeys = ["Summary", "Inputs", "References", "Conditions", "Messages", "Notes"];
const sections = [];
for (const key of orderedKeys) {
if (!(key in model.sections)) {
continue;
}
const line = this.findHeadingLine(lines, key);
if (key === "Inputs") {
sections.push({ label: `Inputs: ${model.inputs.length} rows`, line, ch: 0 });
} else if (key === "References") {
sections.push({ label: `References: ${model.references.length} rows`, line, ch: 0 });
} else if (key === "Messages") {
sections.push({ label: `Messages: ${model.messages.length} rows`, line, ch: 0 });
} else {
sections.push({ label: key, line, ch: 0 });
}
}
return sections;
}
describeMappingSections(model, filePath) {
const lines = this.getFileLines(filePath);
const orderedKeys = ["Summary", "Scope", "Mappings", "Rules", "Notes"];
const sections = [];
for (const key of orderedKeys) {
if (!(key in model.sections)) {
continue;
}
const line = this.findHeadingLine(lines, key);
if (key === "Scope") {
sections.push({ label: `Scope: ${model.scope.length} rows`, line, ch: 0 });
} else if (key === "Mappings") {
sections.push({ label: `Mappings: ${model.mappings.length} rows`, line, ch: 0 });
} else {
sections.push({ label: key, line, ch: 0 });
}
}
return sections;
}
buildScreenSummaryTables(model, filePath) {
const layoutRows = this.readTableRows(filePath, "Layout");
const fieldsRows = this.readTableRows(filePath, "Fields");
const actionsRows = this.readTableRows(filePath, "Actions");
const messagesRows = this.readTableRows(filePath, "Messages");
const tables = [
{
title: "Structure / Layout",
columns: ["id", "label", "kind", "purpose", "notes"],
rows: layoutRows.map((row) => ({
cells: [
row.record.id ?? "",
row.record.label ?? "",
row.record.kind ?? "",
row.record.purpose ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
},
{
title: "UI Elements / Fields",
columns: ["id", "label", "kind", "layout", "ref", "notes"],
rows: fieldsRows.map((row) => ({
cells: [
row.record.id ?? "",
row.record.label ?? "",
row.record.kind ?? "",
row.record.layout ?? "",
this.formatReferenceDisplay(row.record.ref),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
},
{
title: "Behavior / Actions",
columns: [
"id",
"label",
"target",
"event",
"invoke",
"transition",
"transition_status",
"notes"
],
rows: actionsRows.map((row) => ({
cells: [
row.record.id ?? "",
row.record.label ?? "",
row.record.target ?? "",
row.record.event ?? "",
this.formatReferenceDisplay(row.record.invoke),
this.formatReferenceDisplay(row.record.transition),
this.getScreenTransitionStatus(row.record.transition),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
}
];
if (model.messages.length > 0) {
tables.push({
title: "Messages",
columns: ["id", "text", "severity", "timing", "notes"],
rows: messagesRows.map((row) => ({
cells: [
row.record.id ?? "",
this.formatReferenceDisplay(row.record.text),
row.record.severity ?? "",
row.record.timing ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
return tables;
}
collectScreenInvokedProcesses(model) {
const seen = /* @__PURE__ */ new Set();
const items = [];
for (const action of model.actions) {
const invoke = action.invoke?.trim();
if (!invoke) {
continue;
}
const display = this.formatReferenceDisplay(invoke);
const key = `${action.label?.trim() ?? ""}|${display}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
items.push({
label: `${action.label?.trim() || "(action)"} -> ${display}`,
line: action.rowLine,
ch: 0
});
}
return items;
}
collectScreenOutgoingScreens(model) {
const seen = /* @__PURE__ */ new Set();
const items = [];
for (const action of model.actions) {
const transition = action.transition?.trim();
if (!transition) {
continue;
}
const display = this.formatReferenceDisplay(transition);
const key = `${action.label?.trim() ?? ""}|${display}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
const status = this.getScreenTransitionStatus(transition);
items.push({
label: `${action.label?.trim() || "(action)"} -> ${display} [${status}]`,
line: action.rowLine,
ch: 0
});
}
return items;
}
getScreenTransitionStatus(transition) {
const target = transition?.trim();
if (!target) {
return "";
}
const resolved = this.index ? resolveReferenceIdentity(target, this.index) : null;
if (!resolved?.resolvedModel) {
return "unresolved";
}
return resolved.resolvedModel.fileType === "screen" ? "resolved" : "unresolved";
}
resolveFileRenderMode(filePath, fileType, frontmatter, modelKind = null, toolbarOverride = null) {
return resolveRenderMode({
filePath,
formatType: fileType,
modelKind: modelKind ?? (typeof frontmatter.kind === "string" ? frontmatter.kind : null),
toolbarOverride,
frontmatterRenderMode: frontmatter.render_mode,
settingsDefaultRenderMode: this.getDefaultRenderModeForFormat(
fileType,
modelKind ?? (typeof frontmatter.kind === "string" ? frontmatter.kind : null)
)
});
}
getDefaultRenderModeForFormat(fileType, modelKind) {
if (fileType === "diagram") {
if (modelKind === "class") {
return this.settings.defaultClassRenderMode;
}
if (modelKind === "er") {
return this.settings.defaultErRenderMode;
}
return "custom";
}
switch (fileType) {
case "object":
return this.settings.defaultClassRenderMode;
case "er-entity":
return this.settings.defaultErRenderMode;
case "dfd-diagram":
return this.settings.defaultDfdRenderMode;
case "dfd-object":
return "custom";
case "app-process":
return this.settings.defaultProcessRenderMode;
case "screen":
return this.settings.defaultScreenRenderMode;
case "domains":
return this.settings.defaultDomainsViewMode;
case "domain-diagram":
return this.settings.defaultDomainDiagramViewMode;
default:
return "custom";
}
}
getRendererOverrideForLeaf(leaf, filePath) {
const override = this.rendererOverridesByLeaf.get(leaf);
if (!override) {
return null;
}
if (override.filePath !== filePath) {
this.rendererOverridesByLeaf.delete(leaf);
return null;
}
return override.mode;
}
buildRendererSelectionState(file, leaf, resolved, fileType, modelKind) {
const supportedModes = getSupportedRenderModes(fileType, modelKind);
const visibleSelectedMode = supportedModes.includes(resolved.selectedMode) ? resolved.selectedMode : supportedModes[0] ?? "custom";
return {
selectedMode: resolved.selectedMode,
visibleSelectedMode,
supportedModes,
effectiveMode: resolved.effectiveMode,
actualRenderer: resolved.actualRenderer,
source: resolved.source,
fallbackReason: resolved.fallbackReason,
onSelectMode: (mode) => {
this.rendererOverridesByLeaf.set(leaf, { filePath: file.path, mode });
void this.showPreviewForFile(file, leaf, false, "renderer-switch", {
managePreviewLeaf: false
});
}
};
}
buildScreenPreviewTransitions(model) {
const groups = /* @__PURE__ */ new Map();
for (const action of model.actions) {
const transition = action.transition?.trim();
if (!transition) {
continue;
}
const labelInfo = this.buildScreenActionPreviewLabel(action);
const resolved = this.index ? resolveReferenceIdentity(transition, this.index) : { resolvedModel: null };
const resolvedModel = resolved.resolvedModel?.fileType === "screen" ? resolved.resolvedModel : null;
const transitionDisplay = this.formatReferenceDisplay(transition);
const currentScreenId = model.id?.trim();
const currentScreenName = model.name?.trim();
const currentScreenBasename = this.getPathBasename(model.path);
const isSelfTransition = !resolvedModel && (transition.trim() === model.path || Boolean(currentScreenId && transitionDisplay === currentScreenId) || transitionDisplay === currentScreenBasename);
const targetPath = resolvedModel?.path ?? (isSelfTransition ? model.path : void 0);
const targetLabel = resolvedModel?.name?.trim() || resolvedModel?.id?.trim() || (isSelfTransition ? currentScreenName || currentScreenId || currentScreenBasename : transitionDisplay) || transition;
const targetTitle = targetPath ? `${targetLabel}
${targetPath}` : `${targetLabel}
${transition}`;
const targetLinktext = targetPath ? resolvedModel?.id?.trim() || (isSelfTransition ? currentScreenId || model.path : transition) : void 0;
const key = targetPath ? `path:${targetPath}` : `raw:${transition}`;
const group = groups.get(key) ?? {
key,
targetLabel,
targetTitle,
targetPath,
targetLinktext,
sourcePath: model.path,
unresolved: !targetPath,
selfTarget: targetPath === model.path,
actions: []
};
group.actions.push({
label: labelInfo.shortLabel,
fullLabel: labelInfo.fullLabel,
title: [
labelInfo.fullLabel,
action.id?.trim() ? `id: ${action.id.trim()}` : "",
action.target?.trim() ? `target: ${action.target.trim()}` : "",
action.event?.trim() ? `event: ${action.event.trim()}` : "",
`transition: ${targetLabel}`
].filter(Boolean).join("\n"),
line: action.rowLine,
ch: 0
});
groups.set(key, group);
}
return [...groups.values()];
}
buildScreenActionPreviewLabel(action) {
const label = action.label?.trim();
if (label) {
return { shortLabel: label, fullLabel: label };
}
const id = action.id?.trim();
if (id) {
return { shortLabel: id, fullLabel: id };
}
const target = action.target?.trim();
const event = action.event?.trim();
if (target && event) {
const fullLabel = `${target}.${event}`;
return { shortLabel: fullLabel, fullLabel };
}
if (event) {
return { shortLabel: event, fullLabel: event };
}
return { shortLabel: "(action)", fullLabel: "(action)" };
}
buildScreenLayoutBlocks(model) {
const layoutMap = new Map(
model.layouts.map((layout) => [layout.id.trim(), layout]).filter(([layoutId]) => Boolean(layoutId))
);
const fieldsByLayout = /* @__PURE__ */ new Map();
const ungrouped = [];
for (const field of model.fields) {
const item = {
label: field.label?.trim() || field.id,
line: field.rowLine,
ch: 0
};
const layoutId = field.layout?.trim();
if (!layoutId || !layoutMap.has(layoutId)) {
ungrouped.push(item);
continue;
}
const group = fieldsByLayout.get(layoutId) ?? [];
group.push(item);
fieldsByLayout.set(layoutId, group);
}
const blocks = model.layouts.map((layout) => ({
label: layout.label?.trim() ? `${layout.label.trim()} [${layout.id}]` : `[${layout.id}]`,
subtitle: [layout.kind?.trim(), layout.purpose?.trim()].filter(Boolean).join(" / ") || void 0,
line: layout.rowLine,
ch: 0,
items: fieldsByLayout.get(layout.id.trim()) ?? []
}));
if (ungrouped.length > 0) {
blocks.push({
label: "Unassigned",
subtitle: "Layout is missing or undefined",
line: void 0,
ch: 0,
items: ungrouped
});
}
return blocks;
}
buildAppProcessSummaryTables(model, filePath) {
const inputRows = this.readTableRows(filePath, "Inputs");
const outputRows = this.readTableRows(filePath, "Outputs");
const triggerRows = this.readTableRows(filePath, "Triggers");
const transitionRows = this.readTableRows(filePath, "Transitions");
const stepRows = this.readTableRows(filePath, "Steps");
const flowRows = this.readTableRows(filePath, "Flows");
const domainRows = this.readTableRows(filePath, "Domains");
const domainSourceRows = this.readTableRows(filePath, "Domain Sources");
const tables = [];
if (model.triggers.length > 0) {
tables.push({
title: "Triggers Summary",
columns: ["id", "kind", "source", "event", "notes"],
rows: triggerRows.map((row) => ({
cells: [
row.record.id ?? "",
row.record.kind ?? "",
this.formatReferenceDisplay(row.record.source),
row.record.event ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
if ((model.steps?.length ?? 0) > 0) {
tables.push({
title: "Steps Summary",
columns: ["id", "domain", "lane", "label", "kind", "input", "output", "rule", "invoke", "screen", "notes"],
rows: stepRows.map((row) => ({
cells: [
row.record.id ?? "",
row.record.domain ?? "",
row.record.lane ?? "",
row.record.label ?? "",
row.record.kind ?? "",
this.formatReferenceDisplay(row.record.input),
this.formatReferenceDisplay(row.record.output),
this.formatReferenceDisplay(row.record.rule),
this.formatReferenceDisplay(row.record.invoke),
this.formatReferenceDisplay(row.record.screen),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
if (domainSourceRows.length > 0) {
tables.push({
title: "Domain Sources Summary",
columns: ["ref", "notes"],
rows: domainSourceRows.map((row) => ({
cells: [
this.formatReferenceDisplay(row.record.ref),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
if (domainRows.length > 0) {
tables.push({
title: "Domains Summary",
columns: ["id", "name", "kind", "parent", "description"],
rows: domainRows.map((row) => ({
cells: [
row.record.id ?? "",
row.record.name ?? "",
row.record.kind ?? "",
row.record.parent ?? "",
row.record.description ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
tables.push({
title: "Inputs Summary",
columns: ["id", "data", "source", "required", "notes"],
rows: inputRows.map((row) => ({
cells: [
row.record.id ?? "",
this.formatReferenceDisplay(row.record.data),
this.formatReferenceDisplay(row.record.source),
row.record.required ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
tables.push({
title: "Outputs Summary",
columns: ["id", "data", "target", "notes"],
rows: outputRows.map((row) => ({
cells: [
row.record.id ?? "",
this.formatReferenceDisplay(row.record.data),
this.formatReferenceDisplay(row.record.target),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
if (model.transitions.length > 0) {
tables.push({
title: "Transitions Summary",
columns: ["id", "event", "to", "condition", "notes"],
rows: transitionRows.map((row) => ({
cells: [
row.record.id ?? "",
row.record.event ?? "",
this.formatReferenceDisplay(row.record.to),
row.record.condition ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
if (model.hasExplicitFlows) {
tables.push({
title: "Flows Summary",
columns: ["from", "to", "condition", "label", "notes"],
rows: flowRows.map((row) => ({
cells: [
row.record.from ?? "",
row.record.to ?? "",
row.record.condition ?? "",
row.record.label ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
return tables;
}
buildAppProcessBusinessFlowWarnings(model) {
const steps = model.steps ?? [];
if (steps.length === 0 || !model.hasExplicitFlows) {
return [];
}
const stepIds = new Set(steps.map((step) => step.id).filter(Boolean));
const warnings = [];
for (const flow of model.flows ?? []) {
if (!flow.from || !stepIds.has(flow.from)) {
warnings.push({
code: "unresolved-reference",
message: flow.from ? modelWeaveText(
`app_process Flow.from references missing step "${flow.from}"`,
`app_process Flow.from \u304C\u5B58\u5728\u3057\u306A\u3044 step "${flow.from}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`
) : modelWeaveText(
"app_process Flow.from is missing a step id",
"app_process Flow.from \u306E step id \u304C\u3042\u308A\u307E\u305B\u3093\u3002"
),
severity: "warning",
path: model.path,
field: "Flows.from"
});
}
if (!flow.to || !stepIds.has(flow.to)) {
warnings.push({
code: "unresolved-reference",
message: flow.to ? modelWeaveText(
`app_process Flow.to references missing step "${flow.to}"`,
`app_process Flow.to \u304C\u5B58\u5728\u3057\u306A\u3044 step "${flow.to}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`
) : modelWeaveText(
"app_process Flow.to is missing a step id",
"app_process Flow.to \u306E step id \u304C\u3042\u308A\u307E\u305B\u3093\u3002"
),
severity: "warning",
path: model.path,
field: "Flows.to"
});
}
}
return warnings;
}
buildCodeSetSummaryTables(filePath) {
const valueRows = this.readTableRows(filePath, "Values");
return [
{
title: "Values Summary",
columns: ["code", "label", "sort_order", "active", "notes"],
rows: valueRows.map((row) => ({
cells: [
row.record.code ?? "",
row.record.label ?? "",
row.record.sort_order ?? "",
row.record.active ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
}
];
}
buildMessageSummaryTables(filePath) {
const messageRows = this.readTableRows(filePath, "Messages");
return [
{
title: "Messages Summary",
columns: ["message_id", "text", "severity", "timing", "audience", "active", "notes"],
rows: messageRows.map((row) => ({
cells: [
row.record.message_id ?? "",
row.record.text ?? "",
row.record.severity ?? "",
row.record.timing ?? "",
row.record.audience ?? "",
row.record.active ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
}
];
}
buildRuleSummaryTables(model, filePath) {
const inputRows = this.readTableRows(filePath, "Inputs");
const referenceRows = this.readTableRows(filePath, "References");
const messageRows = this.readTableRows(filePath, "Messages");
const tables = [
{
title: "Inputs Summary",
columns: ["id", "data", "source", "required", "notes"],
rows: inputRows.map((row) => ({
cells: [
row.record.id ?? "",
this.formatReferenceDisplay(row.record.data),
this.formatReferenceDisplay(row.record.source),
row.record.required ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
},
{
title: "References Summary",
columns: ["ref", "usage", "notes"],
rows: referenceRows.map((row) => ({
cells: [
this.formatReferenceDisplay(row.record.ref),
row.record.usage ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
}
];
if (model.messages.length > 0) {
tables.push({
title: "Messages Summary",
columns: ["severity", "message", "condition", "notes"],
rows: messageRows.map((row) => ({
cells: [
row.record.severity ?? "",
this.formatReferenceDisplay(row.record.message),
row.record.condition ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
});
}
return tables;
}
buildMappingSummaryTables(filePath) {
const scopeRows = this.readTableRows(filePath, "Scope");
const mappingRows = this.readTableRows(filePath, "Mappings");
return [
{
title: "Scope Summary",
columns: ["role", "ref", "notes"],
rows: scopeRows.map((row) => ({
cells: [
row.record.role ?? "",
this.formatReferenceDisplay(row.record.ref),
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
},
{
title: "Mappings Summary",
columns: ["target_ref", "source_ref", "transform", "rule", "required", "notes"],
rows: mappingRows.map((row) => ({
cells: [
this.formatReferenceDisplay(row.record.target_ref),
this.formatReferenceDisplay(row.record.source_ref),
row.record.transform ?? "",
this.formatReferenceDisplay(row.record.rule),
row.record.required ?? "",
row.record.notes ?? ""
],
line: row.line,
ch: row.ch
}))
}
];
}
collectScreenLocalProcesses(filePath) {
const lines = this.getFileLines(filePath);
const sectionLine = this.findHeadingLine(lines, "Local Processes");
if (sectionLine === void 0) {
return [];
}
const results = [];
for (let index = sectionLine + 1; index < lines.length; index += 1) {
const line = lines[index] ?? "";
const trimmed = line.trim();
if (/^##\s+/.test(trimmed)) {
break;
}
const match = trimmed.match(/^###\s+(.+)$/);
if (!match) {
continue;
}
results.push({
label: match[1].trim(),
line: index,
ch: Math.max(0, line.indexOf("###"))
});
}
return results;
}
formatReferenceDisplay(value) {
const trimmed = value?.trim();
if (!trimmed) {
return "";
}
const qualified = parseQualifiedRef(trimmed);
if (qualified?.hasMemberRef) {
const baseLabel = this.formatBaseReferenceDisplay(qualified.baseRefRaw);
return qualified.memberRef ? `${baseLabel}.${qualified.memberRef}` : baseLabel;
}
return this.formatBaseReferenceDisplay(trimmed);
}
formatBaseReferenceDisplay(value) {
const parsed = parseReferenceValue(value);
if (!parsed) {
return value;
}
if (parsed.display?.trim()) {
return parsed.display.trim();
}
if (parsed.target?.trim()) {
return this.getPathBasename(parsed.target.trim());
}
return parsed.raw || value;
}
getFileLines(filePath) {
const content = this.index?.sourceFilesByPath[filePath]?.content ?? "";
return content.split(/\r?\n/);
}
findHeadingLine(lines, sectionName) {
const heading = `## ${sectionName}`;
for (let index = 0; index < lines.length; index += 1) {
if ((lines[index] ?? "").trim() === heading) {
return index;
}
}
return void 0;
}
readTableRows(filePath, sectionName, filterColumns) {
const lines = this.getFileLines(filePath);
const sectionLine = this.findHeadingLine(lines, sectionName);
if (sectionLine === void 0) {
return [];
}
let header = null;
const rows = [];
for (let index = sectionLine + 1; index < lines.length; index += 1) {
const line = lines[index] ?? "";
const trimmed = line.trim();
if (/^##\s+/.test(trimmed)) {
break;
}
if (!trimmed.startsWith("|")) {
continue;
}
if (this.isMarkdownSeparatorLine(line)) {
continue;
}
const values = splitMarkdownTableRow(line);
if (!values) {
continue;
}
if (!header) {
header = values;
continue;
}
const record = {};
for (let columnIndex = 0; columnIndex < header.length; columnIndex += 1) {
record[header[columnIndex]] = values[columnIndex] ?? "";
}
if (Object.values(record).every((value) => !value.trim())) {
continue;
}
if (filterColumns) {
const filtered = {};
for (const key of filterColumns) {
filtered[key] = record[key] ?? "";
}
rows.push({
record: filtered,
line: index,
ch: getMarkdownTableCellRanges(line)?.[0]?.contentStart ?? 0
});
} else {
rows.push({
record,
line: index,
ch: getMarkdownTableCellRanges(line)?.[0]?.contentStart ?? 0
});
}
}
return rows;
}
isMarkdownSeparatorLine(line) {
const cells = splitMarkdownTableRow(line);
return Boolean(cells && cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell)));
}
async openObjectNote(objectId, sourcePath, navigation) {
if (!this.index) {
await this.rebuildIndex();
}
if (!this.index) {
new import_obsidian8.Notice(modelWeaveText(
"Model index is not available",
"Model index \u304C\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3092\u518D\u69CB\u7BC9\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
));
return;
}
const result = await openModelObjectNote(this.app, this.index, objectId, {
sourcePath,
openInNewLeaf: navigation?.openInNewLeaf ?? false
});
if (!result.ok) {
new import_obsidian8.Notice(result.reason ?? `Could not open object "${objectId}"`);
return;
}
await this.syncPreviewToActiveFile(false, "viewer-node-navigation");
}
async openDiagnosticLocation(filePath, diagnostic) {
const targetPath = diagnostic.filePath ?? diagnostic.path ?? filePath;
const abstractFile = this.app.vault.getAbstractFileByPath(targetPath);
if (!(abstractFile instanceof import_obsidian8.TFile)) {
return;
}
const activeMarkdownView = this.app.workspace.getActiveViewOfType(import_obsidian8.MarkdownView);
let targetLeaf = activeMarkdownView?.file?.path === targetPath ? activeMarkdownView.leaf : this.findMarkdownLeafForPath(targetPath);
if (!targetLeaf) {
targetLeaf = this.app.workspace.getMostRecentLeaf();
if (targetLeaf && this.isPreviewLeaf(targetLeaf)) {
targetLeaf = this.app.workspace.getLeaf(true);
}
}
if (!targetLeaf) {
return;
}
if (targetLeaf.view.file?.path !== targetPath) {
await targetLeaf.openFile(abstractFile);
}
this.app.workspace.setActiveLeaf(targetLeaf, { focus: true });
const markdownView = targetLeaf.view instanceof import_obsidian8.MarkdownView ? targetLeaf.view : this.app.workspace.getActiveViewOfType(import_obsidian8.MarkdownView);
const editor = markdownView?.editor;
if (!editor) {
return;
}
const content = editor.getValue();
const targetLine = resolveDiagnosticLine(content, diagnostic);
await this.openFileLocation(targetPath, targetLine, 0, targetLeaf);
}
async openFileLocation(filePath, line, ch = 0, preferredLeaf) {
const abstractFile = this.app.vault.getAbstractFileByPath(filePath);
if (!(abstractFile instanceof import_obsidian8.TFile)) {
return;
}
const activeMarkdownView = this.app.workspace.getActiveViewOfType(import_obsidian8.MarkdownView);
let targetLeaf = preferredLeaf ?? (activeMarkdownView?.file?.path === filePath ? activeMarkdownView.leaf : this.findMarkdownLeafForPath(filePath));
if (!targetLeaf) {
targetLeaf = this.app.workspace.getMostRecentLeaf();
if (targetLeaf && this.isPreviewLeaf(targetLeaf)) {
targetLeaf = this.app.workspace.getLeaf(true);
}
}
if (!targetLeaf) {
return;
}
if (targetLeaf.view.file?.path !== filePath) {
await targetLeaf.openFile(abstractFile);
}
this.app.workspace.setActiveLeaf(targetLeaf, { focus: true });
const markdownView = targetLeaf.view instanceof import_obsidian8.MarkdownView ? targetLeaf.view : this.app.workspace.getActiveViewOfType(import_obsidian8.MarkdownView);
const editor = markdownView?.editor;
if (!editor) {
return;
}
editor.setCursor({ line, ch });
editor.scrollIntoView(
{
from: { line, ch },
to: { line, ch }
},
true
);
editor.focus?.();
editor.cm?.focus?.();
}
async openReferencedFile(filePath, openInNewLeaf = false) {
const preferredLeaf = openInNewLeaf ? this.app.workspace.getLeaf(true) : void 0;
await this.openFileLocation(filePath, 0, 0, preferredLeaf);
}
findMarkdownLeafForPath(filePath) {
for (const leaf of this.app.workspace.getLeavesOfType("markdown")) {
const viewFile = leaf.view.file ?? null;
if (viewFile?.path === filePath) {
return leaf;
}
}
return null;
}
async ensurePreviewLeaf(preferredLeaf, activate = true, managePreviewLeaf = true) {
const leaf = preferredLeaf ?? await this.findOrCreatePreviewLeaf();
await leaf.setViewState({
type: MODELING_PREVIEW_VIEW_TYPE,
active: activate
});
if (managePreviewLeaf) {
this.previewLeaf = leaf;
}
return leaf;
}
async findOrCreatePreviewLeaf() {
const existing = this.getManagedPreviewLeaf();
if (existing) {
await this.closeDuplicatePreviewLeaves(existing);
return existing;
}
const leaf = this.app.workspace.getRightLeaf(false) ?? this.app.workspace.getLeaf(true);
this.previewLeaf = leaf;
return leaf;
}
getManagedPreviewLeaf() {
if (this.previewLeaf && this.isPreviewLeaf(this.previewLeaf)) {
return this.previewLeaf;
}
const leaves = this.getAllPreviewLeaves();
if (leaves.length === 0) {
this.previewLeaf = null;
return null;
}
this.previewLeaf = leaves[0];
return this.previewLeaf;
}
async findExportableModelWeaveView() {
const candidateLeaves = [];
const mostRecentLeaf = this.app.workspace.getMostRecentLeaf();
if (mostRecentLeaf) {
candidateLeaves.push(mostRecentLeaf);
}
if (this.previewLeaf) {
candidateLeaves.push(this.previewLeaf);
}
candidateLeaves.push(...this.getAllPreviewLeaves());
const orderedLeaves = Array.from(new Set(candidateLeaves));
const loadedViews = [];
for (const leaf of orderedLeaves) {
if (!this.isPreviewLeaf(leaf)) {
continue;
}
await leaf.loadIfDeferred();
const view = leaf.view;
if (view instanceof ModelingPreviewView) {
loadedViews.push(view);
if (this.isExportablePreviewView(view)) {
this.previewLeaf = leaf;
return view;
}
}
}
if (loadedViews.length > 0) {
return loadedViews[0];
}
return null;
}
isExportablePreviewView(view) {
const container = view.contentEl;
if (!container?.isConnected) {
return false;
}
if (container.getClientRects().length > 0) {
return true;
}
return container.clientWidth > 0 || container.clientHeight > 0;
}
getAllPreviewLeaves() {
const leaves = [
...this.app.workspace.getLeavesOfType(MODELING_PREVIEW_VIEW_TYPE),
...LEGACY_PREVIEW_VIEW_TYPES.flatMap(
(viewType) => this.app.workspace.getLeavesOfType(viewType)
)
];
return Array.from(new Set(leaves));
}
async closeDuplicatePreviewLeaves(keepLeaf) {
const duplicates = this.getAllPreviewLeaves().filter((leaf) => leaf !== keepLeaf);
for (const leaf of duplicates) {
await leaf.loadIfDeferred();
leaf.detach();
}
}
isPreviewLeaf(leaf) {
const viewType = leaf.view.getViewType();
return viewType === MODELING_PREVIEW_VIEW_TYPE || LEGACY_PREVIEW_VIEW_TYPES.includes(
viewType
);
}
async normalizePreviewLeaves() {
const leaves = this.getAllPreviewLeaves();
if (leaves.length === 0) {
return;
}
const keepLeaf = leaves[0];
await keepLeaf.loadIfDeferred();
await keepLeaf.setViewState({
type: MODELING_PREVIEW_VIEW_TYPE,
active: false
});
this.previewLeaf = keepLeaf;
await this.closeDuplicatePreviewLeaves(keepLeaf);
}
};
function resolveDiagnosticLine(content, diagnostic) {
if (typeof diagnostic.line === "number" && diagnostic.line >= 0) {
return diagnostic.line;
}
if (typeof diagnostic.fromLine === "number" && diagnostic.fromLine >= 0) {
return diagnostic.fromLine;
}
if (typeof diagnostic.toLine === "number" && diagnostic.toLine >= 0) {
return diagnostic.toLine;
}
const lines = content.split(/\r?\n/);
const frontmatterField = typeof diagnostic.field === "string" ? diagnostic.field : "";
const section = resolveDiagnosticSection(diagnostic);
if (frontmatterField && isFrontmatterField(frontmatterField)) {
const frontmatterLine = findFrontmatterFieldLine(lines, frontmatterField);
if (frontmatterLine >= 0) {
return frontmatterLine;
}
}
const relatedId = typeof diagnostic.context?.relatedId === "string" ? diagnostic.context.relatedId : null;
if (section === "Relations" && relatedId) {
const relationBlockLine = findLineIndex(lines, (line) => line.trim() === `### ${relatedId}`);
if (relationBlockLine >= 0) {
return relationBlockLine;
}
const relationRowLine = findLineIndex(lines, (line) => line.includes(`| ${relatedId} |`));
if (relationRowLine >= 0) {
return relationRowLine;
}
}
if (section) {
const sectionLine = findLineIndex(lines, (line) => line.trim() === `## ${section}`);
if (sectionLine >= 0) {
const rowIndex = getDiagnosticRowIndex(diagnostic);
if (typeof rowIndex === "number") {
const rowLine = findDiagnosticTableRowLine(lines, sectionLine, rowIndex);
if (rowLine >= 0) {
return rowLine;
}
}
return sectionLine;
}
}
return 0;
}
function resolveDiagnosticSection(diagnostic) {
if (typeof diagnostic.section === "string" && diagnostic.section.trim()) {
return diagnostic.section.trim();
}
const contextSection = typeof diagnostic.context?.section === "string" ? diagnostic.context.section : null;
if (contextSection) {
return contextSection;
}
const field = typeof diagnostic.field === "string" ? diagnostic.field : "";
if (field.startsWith("Relations:")) {
return "Relations";
}
const fieldToSection = {
objectRefs: "Objects",
relations: "Relations",
relatedObjects: "Relations",
Attributes: "Attributes",
Methods: "Methods",
Relations: "Relations",
Objects: "Objects",
Columns: "Columns",
Indexes: "Indexes",
Notes: "Notes",
Summary: "Summary",
Overview: "Overview"
};
if (fieldToSection[field]) {
return fieldToSection[field];
}
const fieldSection = field.split(".")[0]?.trim();
return fieldSection || null;
}
function getDiagnosticRowIndex(diagnostic) {
const rawRowIndex = diagnostic.context?.rowIndex;
if (typeof rawRowIndex === "number" && Number.isFinite(rawRowIndex)) {
return rawRowIndex;
}
if (typeof rawRowIndex === "string") {
const parsed = Number.parseInt(rawRowIndex, 10);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
function findDiagnosticTableRowLine(lines, sectionLine, rowIndex) {
if (rowIndex < 1) {
return -1;
}
let tableRow = 0;
let tableStarted = false;
for (let index = sectionLine + 1; index < lines.length; index += 1) {
const trimmed = lines[index].trim();
if (trimmed.startsWith("## ")) {
return -1;
}
if (!trimmed.startsWith("|")) {
continue;
}
tableStarted = true;
if (/^\|?\s*:?-{3,}:?/.test(trimmed)) {
continue;
}
tableRow += 1;
if (tableRow === rowIndex + 1) {
return index;
}
}
return tableStarted ? sectionLine : -1;
}
function isFrontmatterField(field) {
return [
"type",
"id",
"name",
"kind",
"logical_name",
"physical_name",
"schema_name",
"dbms",
"package",
"stereotype"
].includes(field);
}
function findFrontmatterFieldLine(lines, field) {
if ((lines[0] ?? "").trim() !== "---") {
return -1;
}
for (let index = 1; index < lines.length; index += 1) {
const trimmed = lines[index].trim();
if (trimmed === "---") {
break;
}
if (trimmed.startsWith(`${field}:`)) {
return index;
}
}
return -1;
}
function findLineIndex(lines, predicate) {
for (let index = 0; index < lines.length; index += 1) {
if (predicate(lines[index] ?? "")) {
return index;
}
}
return -1;
}
var ModelWeaveSettingTab = class extends import_obsidian8.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
const settings = this.plugin.getSettings();
const t = createModelWeaveTranslator(settings.uiLanguage);
containerEl.empty();
new import_obsidian8.Setting(containerEl).setName(t("settings.uiLanguage.name")).setDesc(t("settings.uiLanguage.desc")).addDropdown((dropdown) => {
dropdown.addOption("auto", t("settings.option.auto")).addOption("en", t("settings.option.english")).addOption("ja", t("settings.option.japanese")).setValue(settings.uiLanguage).onChange(async (value) => {
if (!isUiLanguageOption(value)) {
return;
}
await this.plugin.updateSettings({
uiLanguage: value
});
this.display();
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.section.viewer")).setHeading();
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultClassRenderMode.name")).setDesc(t("settings.defaultClassRenderMode.desc")).addDropdown((dropdown) => {
dropdown.addOption("custom", t("settings.option.custom")).addOption("mermaid", t("settings.option.mermaid")).addOption("mermaid-detail", t("settings.option.mermaidDetail")).setValue(settings.defaultClassRenderMode).onChange(async (value) => {
if (!isClassRenderModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultClassRenderMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultErRenderMode.name")).setDesc(t("settings.defaultErRenderMode.desc")).addDropdown((dropdown) => {
dropdown.addOption("custom", t("settings.option.custom")).addOption("mermaid", t("settings.option.mermaid")).addOption("mermaid-detail", t("settings.option.mermaidDetail")).setValue(settings.defaultErRenderMode).onChange(async (value) => {
if (!isErRenderModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultErRenderMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultDfdRenderMode.name")).setDesc(t("settings.defaultDfdRenderMode.desc")).addDropdown((dropdown) => {
dropdown.addOption("mermaid", t("settings.option.mermaid")).setValue(settings.defaultDfdRenderMode).onChange(async (value) => {
if (!isDfdRenderModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultDfdRenderMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultProcessRenderMode.name")).setDesc(t("settings.defaultProcessRenderMode.desc")).addDropdown((dropdown) => {
dropdown.addOption("custom", t("settings.option.custom")).setValue(settings.defaultProcessRenderMode).onChange(async (value) => {
if (!isProcessRenderModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultProcessRenderMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultBusinessFlowDirection.name")).setDesc(t("settings.defaultBusinessFlowDirection.desc")).addDropdown((dropdown) => {
dropdown.addOption("LR", t("settings.defaultBusinessFlowDirection.lr")).addOption("TD", t("settings.defaultBusinessFlowDirection.td")).setValue(settings.defaultBusinessFlowDirection).onChange(async (value) => {
if (!isBusinessFlowDirectionOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultBusinessFlowDirection: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultFlowDiagramViewMode.name")).setDesc(t("settings.defaultFlowDiagramViewMode.desc")).addDropdown((dropdown) => {
dropdown.addOption("detail", t("flowDiagram.viewMode.detail")).addOption("screen", t("flowDiagram.viewMode.screen")).setValue(settings.defaultFlowDiagramViewMode).onChange(async (value) => {
if (!isFlowDiagramViewModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultFlowDiagramViewMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultScreenRenderMode.name")).setDesc(t("settings.defaultScreenRenderMode.desc")).addDropdown((dropdown) => {
dropdown.addOption("custom", t("settings.option.custom")).setValue(settings.defaultScreenRenderMode).onChange(async (value) => {
if (!isScreenRenderModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultScreenRenderMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultDomainsViewMode.name")).setDesc(t("settings.defaultDomainsViewMode.desc")).addDropdown((dropdown) => {
for (const option of DOMAIN_VIEW_MODE_SETTING_OPTIONS) {
dropdown.addOption(option.value, option.label);
}
dropdown.setValue(settings.defaultDomainsViewMode).onChange(async (value) => {
if (!isDomainViewModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultDomainsViewMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultDomainDiagramViewMode.name")).setDesc(t("settings.defaultDomainDiagramViewMode.desc")).addDropdown((dropdown) => {
for (const option of DOMAIN_VIEW_MODE_SETTING_OPTIONS) {
dropdown.addOption(option.value, option.label);
}
dropdown.setValue(settings.defaultDomainDiagramViewMode).onChange(async (value) => {
if (!isDomainViewModeOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultDomainDiagramViewMode: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultZoom.name")).setDesc(t("settings.defaultZoom.desc")).addDropdown((dropdown) => {
dropdown.addOption("fit", t("settings.option.fit")).addOption("100", "100%").setValue(settings.defaultZoom).onChange(async (value) => {
if (!isDefaultZoomOption(value)) {
return;
}
await this.plugin.updateSettings({
defaultZoom: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.fontSize.name")).setDesc(t("settings.fontSize.desc")).addDropdown((dropdown) => {
dropdown.addOption("small", t("settings.option.small")).addOption("normal", t("settings.option.normal")).addOption("large", t("settings.option.large")).setValue(settings.fontSize).onChange(async (value) => {
if (!isFontSizeOption(value)) {
return;
}
await this.plugin.updateSettings({
fontSize: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.nodeDensity.name")).setDesc(t("settings.nodeDensity.desc")).addDropdown((dropdown) => {
dropdown.addOption("compact", t("settings.option.compact")).addOption("normal", t("settings.option.normal")).addOption("relaxed", t("settings.option.relaxed")).setValue(settings.nodeDensity).onChange(async (value) => {
if (!isNodeDensityOption(value)) {
return;
}
await this.plugin.updateSettings({
nodeDensity: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.relationshipView.name")).setDesc(t("settings.relationshipView.desc")).addToggle((toggle) => {
toggle.setValue(settings.enableRelationshipView).onChange(async (value) => {
await this.plugin.updateSettings({
enableRelationshipView: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.showMermaidRenderDebug.name")).setDesc(t("settings.showMermaidRenderDebug.desc")).addToggle((toggle) => {
toggle.setValue(settings.showMermaidRenderDebug).onChange(async (value) => {
await this.plugin.updateSettings({
showMermaidRenderDebug: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.localSourceRoot.name")).setDesc(t("settings.localSourceRoot.desc")).addText((text) => {
text.setPlaceholder("/path/to/source/checkout").setValue(settings.localSourceRoot).onChange(async (value) => {
await this.plugin.updateSettings({
localSourceRoot: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.defaultColorScheme.name")).setDesc(t("settings.defaultColorScheme.desc")).addText((text) => {
text.setPlaceholder("[[color-scheme-default]]").setValue(settings.defaultColorSchemeRef ?? "").onChange(async (value) => {
await this.plugin.updateSettings({
defaultColorSchemeRef: value
});
});
});
new import_obsidian8.Setting(containerEl).setName(t("settings.refreshOpenViews.name")).setDesc(t("settings.refreshOpenViews.desc")).addButton((button) => {
button.setButtonText(t("settings.refreshOpenViews.button")).onClick(async () => {
await this.plugin.refreshOpenModelWeaveViews();
new import_obsidian8.Notice(t("settings.refreshOpenViews.notice"));
});
});
}
};