"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")); }); }); } };