feat: add diagnostic detail guidance

This commit is contained in:
nejimakibird 2026-06-19 22:26:46 +09:00
parent bce78ced0f
commit e7c57023e8
6 changed files with 591 additions and 0 deletions

261
main.js
View file

@ -17982,6 +17982,8 @@ var EN_MESSAGES = {
"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.severity.error": "Error",
"diagnostics.severity.warning": "Warning",
"diagnostics.severity.note": "Note",
@ -17990,9 +17992,26 @@ var EN_MESSAGES = {
"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",
"objectContext.title": "Related objects",
"objectContext.linked": "{count} linked",
"objectContext.connectionDetails": "Connection details",
@ -18319,6 +18338,8 @@ var JA_MESSAGES = {
"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.severity.error": "\u30A8\u30E9\u30FC",
"diagnostics.severity.warning": "\u8B66\u544A",
"diagnostics.severity.note": "\u30CE\u30FC\u30C8",
@ -18327,9 +18348,26 @@ var JA_MESSAGES = {
"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",
"objectContext.title": "\u95A2\u9023\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8",
"objectContext.linked": "{count} \u4EF6\u306E\u95A2\u9023",
"objectContext.connectionDetails": "\u63A5\u7D9A\u8A73\u7D30",
@ -23028,6 +23066,20 @@ function renderDiagnosticCard(container, diagnostic, onOpenDiagnostic, t, langua
item.createSpan({ text: entry.value, cls: "model-weave-diagnostic-meta-value" });
}
}
const details = getDiagnosticDetailEntries(diagnostic, t);
if (details.length > 0) {
const detailBox = card.createDiv({ cls: "model-weave-diagnostic-detail-box" });
detailBox.createDiv({
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" });
}
}
const actions = card.createDiv({ cls: "model-weave-diagnostic-actions" });
if (onOpenDiagnostic) {
const openButton = actions.createEl("button", {
@ -23062,6 +23114,24 @@ function renderDiagnosticCard(container, diagnostic, onOpenDiagnostic, t, langua
"model-weave-diagnostic-action"
);
}
const expectedHeader = getExpectedHeaderForDiagnostic(diagnostic);
if (expectedHeader) {
renderDiagnosticCopyButton(
actions,
t("diagnostics.copyExpectedHeader"),
expectedHeader,
"model-weave-diagnostic-action"
);
}
const frontmatterExample = getFrontmatterExampleForDiagnostic(diagnostic);
if (frontmatterExample) {
renderDiagnosticCopyButton(
actions,
t("diagnostics.copyFrontmatterExample"),
frontmatterExample,
"model-weave-diagnostic-action"
);
}
}
function renderDiagnosticCopyButton(container, label, value, className) {
const button = container.createEl("button", {
@ -23083,6 +23153,190 @@ function getDiagnosticSeverityLabel(severity, t) {
}
return t("diagnostics.severity.note");
}
function getDiagnosticDetailEntries(diagnostic, t) {
const details = [];
const expectedHeader = getExpectedHeaderForDiagnostic(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 = getDiagnosticSectionName(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 = getDiagnosticStringValue(diagnostic.context?.rowIndex);
if (row && /row|reference|mapping/i.test(diagnostic.message)) {
details.push({ label: t("diagnostics.meta.row"), value: row });
}
const field = getDiagnosticStringValue(diagnostic.context?.field) ?? diagnostic.field;
if (field && /reference|field/i.test(diagnostic.message)) {
details.push({ label: t("diagnostics.meta.field"), value: field });
}
return dedupeDiagnosticDetailEntries(details);
}
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 getExpectedHeaderForDiagnostic(diagnostic) {
const contextHeader = findDiagnosticContextValue(diagnostic.context, [
"expectedHeader",
"expectedHeaders",
"expected"
]);
if (contextHeader) {
return contextHeader;
}
if (!/table columns in section/i.test(diagnostic.message)) {
return null;
}
const message = diagnostic.message;
if (/screen field headers/i.test(message)) {
return "id | label | kind | layout | rule | ref | default | required | notes";
}
if (/legacy headers/i.test(message) && /Transitions/i.test(message)) {
return "id | event | to | condition | notes";
}
if (/supported DFD object headers/i.test(message)) {
return "id | label | kind | ref | domain | notes";
}
if (/supported class relation headers/i.test(message)) {
return "id | from | to | kind | label | notes";
}
if (/Domain Sources headers/i.test(message)) {
return "ref | notes";
}
if (/app_process step headers/i.test(message)) {
return "id | domain | label | kind | input | output | rule | invoke | screen | notes";
}
const section = getDiagnosticSectionName(diagnostic);
return section ? getGenericExpectedHeaderForSection(section) : null;
}
function getGenericExpectedHeaderForSection(section) {
const normalized = section.trim().toLowerCase();
if (normalized === "mappings") {
return "source_ref | target_ref | transform | rule | required | notes";
}
if (normalized === "layout") {
return "id | label | kind | parent | order | notes";
}
if (normalized === "actions") {
return "id | target | event | kind | invoke | transition | rule | condition | notes";
}
if (normalized === "messages") {
return "id | timing | severity | audience | text | notes";
}
if (normalized === "objects") {
return "id | label | kind | ref | domain | notes";
}
if (normalized === "flows") {
return "id | from | to | label | kind | notes";
}
if (normalized === "domain sources") {
return "ref | notes";
}
return null;
}
function getDiagnosticSectionName(diagnostic) {
const contextSection = getDiagnosticStringValue(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) {
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;
@ -23144,6 +23398,10 @@ function getDiagnosticStringValue(value) {
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) {
@ -23159,6 +23417,9 @@ function formatDiagnosticAsMarkdown(diagnostic, localizedMessage, t) {
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 toModelWeaveUiLanguage(language) {

View file

@ -101,6 +101,8 @@ export const EN_MESSAGES: ModelWeaveMessageDictionary = {
"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.severity.error": "Error",
"diagnostics.severity.warning": "Warning",
"diagnostics.severity.note": "Note",
@ -109,9 +111,26 @@ export const EN_MESSAGES: ModelWeaveMessageDictionary = {
"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",
"objectContext.title": "Related objects",
"objectContext.linked": "{count} linked",
"objectContext.connectionDetails": "Connection details",

View file

@ -95,6 +95,8 @@ export const JA_MESSAGES: ModelWeaveMessageDictionary = {
"diagnostics.copyMessage": "メッセージをコピー",
"diagnostics.copyMarkdown": "診断をMarkdownでコピー",
"diagnostics.copyReference": "参照値をコピー",
"diagnostics.copyExpectedHeader": "期待ヘッダーをコピー",
"diagnostics.copyFrontmatterExample": "frontmatter 例をコピー",
"diagnostics.severity.error": "エラー",
"diagnostics.severity.warning": "警告",
"diagnostics.severity.note": "ノート",
@ -103,9 +105,26 @@ export const JA_MESSAGES: ModelWeaveMessageDictionary = {
"diagnostics.meta.line": "行",
"diagnostics.meta.row": "行番号",
"diagnostics.meta.reference": "参照値",
"diagnostics.meta.field": "フィールド",
"diagnostics.meta.severity": "重要度",
"diagnostics.meta.code": "コード",
"diagnostics.meta.message": "メッセージ",
"diagnostics.details.title": "診断詳細",
"diagnostics.details.expectedHeader": "期待されるヘッダー",
"diagnostics.details.actualHeader": "実際のヘッダー",
"diagnostics.details.missingColumns": "不足している列",
"diagnostics.details.extraColumns": "余分な列",
"diagnostics.details.unresolvedReference": "未解決の参照",
"diagnostics.details.duplicateTarget": "重複している対象",
"diagnostics.details.mappingRow": "Mapping row",
"diagnostics.details.frontmatterKey": "不足している frontmatter key",
"diagnostics.details.frontmatterExample": "frontmatter 例",
"diagnostics.details.requestedRenderMode": "要求された描画モード",
"diagnostics.details.effectiveRenderMode": "実際の描画モード",
"diagnostics.details.formatDefault": "format 既定",
"diagnostics.details.diagramCompatibility": "図との互換性",
"diagnostics.details.notClassDiagramCompatible": "存在しますが、Class Diagram の描画対象外です",
"diagnostics.details.targetType": "参照先 type",
"objectContext.title": "関連オブジェクト",
"objectContext.linked": "{count} 件の関連",
"objectContext.connectionDetails": "接続詳細",

View file

@ -4903,6 +4903,21 @@ function renderDiagnosticCard(
}
}
const details = getDiagnosticDetailEntries(diagnostic, t);
if (details.length > 0) {
const detailBox = card.createDiv({ cls: "model-weave-diagnostic-detail-box" });
detailBox.createDiv({
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" });
}
}
const actions = card.createDiv({ cls: "model-weave-diagnostic-actions" });
if (onOpenDiagnostic) {
const openButton = actions.createEl("button", {
@ -4939,6 +4954,26 @@ function renderDiagnosticCard(
"model-weave-diagnostic-action"
);
}
const expectedHeader = getExpectedHeaderForDiagnostic(diagnostic);
if (expectedHeader) {
renderDiagnosticCopyButton(
actions,
t("diagnostics.copyExpectedHeader"),
expectedHeader,
"model-weave-diagnostic-action"
);
}
const frontmatterExample = getFrontmatterExampleForDiagnostic(diagnostic);
if (frontmatterExample) {
renderDiagnosticCopyButton(
actions,
t("diagnostics.copyFrontmatterExample"),
frontmatterExample,
"model-weave-diagnostic-action"
);
}
}
function renderDiagnosticCopyButton(
@ -4971,6 +5006,229 @@ function getDiagnosticSeverityLabel(
return t("diagnostics.severity.note");
}
interface DiagnosticDetailEntry {
label: string;
value: string;
}
function getDiagnosticDetailEntries(
diagnostic: ValidationWarning,
t: ModelWeaveTranslator
): DiagnosticDetailEntry[] {
const details: DiagnosticDetailEntry[] = [];
const expectedHeader = getExpectedHeaderForDiagnostic(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 = getDiagnosticSectionName(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 = getDiagnosticStringValue(diagnostic.context?.rowIndex);
if (row && /row|reference|mapping/i.test(diagnostic.message)) {
details.push({ label: t("diagnostics.meta.row"), value: row });
}
const field = getDiagnosticStringValue(diagnostic.context?.field) ?? diagnostic.field;
if (field && /reference|field/i.test(diagnostic.message)) {
details.push({ label: t("diagnostics.meta.field"), value: field });
}
return dedupeDiagnosticDetailEntries(details);
}
function dedupeDiagnosticDetailEntries(entries: DiagnosticDetailEntry[]): DiagnosticDetailEntry[] {
const seen = new Set<string>();
const result: DiagnosticDetailEntry[] = [];
for (const entry of entries) {
const key = entry.label + "\u0000" + entry.value;
if (seen.has(key)) {
continue;
}
seen.add(key);
result.push(entry);
}
return result;
}
function getExpectedHeaderForDiagnostic(diagnostic: ValidationWarning): string | null {
const contextHeader = findDiagnosticContextValue(diagnostic.context, [
"expectedHeader",
"expectedHeaders",
"expected"
]);
if (contextHeader) {
return contextHeader;
}
if (!/table columns in section/i.test(diagnostic.message)) {
return null;
}
const message = diagnostic.message;
if (/screen field headers/i.test(message)) {
return "id | label | kind | layout | rule | ref | default | required | notes";
}
if (/legacy headers/i.test(message) && /Transitions/i.test(message)) {
return "id | event | to | condition | notes";
}
if (/supported DFD object headers/i.test(message)) {
return "id | label | kind | ref | domain | notes";
}
if (/supported class relation headers/i.test(message)) {
return "id | from | to | kind | label | notes";
}
if (/Domain Sources headers/i.test(message)) {
return "ref | notes";
}
if (/app_process step headers/i.test(message)) {
return "id | domain | label | kind | input | output | rule | invoke | screen | notes";
}
const section = getDiagnosticSectionName(diagnostic);
return section ? getGenericExpectedHeaderForSection(section) : null;
}
function getGenericExpectedHeaderForSection(section: string): string | null {
const normalized = section.trim().toLowerCase();
if (normalized === "mappings") {
return "source_ref | target_ref | transform | rule | required | notes";
}
if (normalized === "layout") {
return "id | label | kind | parent | order | notes";
}
if (normalized === "actions") {
return "id | target | event | kind | invoke | transition | rule | condition | notes";
}
if (normalized === "messages") {
return "id | timing | severity | audience | text | notes";
}
if (normalized === "objects") {
return "id | label | kind | ref | domain | notes";
}
if (normalized === "flows") {
return "id | from | to | label | kind | notes";
}
if (normalized === "domain sources") {
return "ref | notes";
}
return null;
}
function getDiagnosticSectionName(diagnostic: ValidationWarning): string | null {
const contextSection = getDiagnosticStringValue(diagnostic.context?.section);
if (contextSection) {
return contextSection;
}
const quoted = diagnostic.message.match(/section "([^"]+)"/i)?.[1];
return quoted ?? diagnostic.section ?? null;
}
function getDuplicateMappingRowValue(diagnostic: ValidationWarning): string | null {
if (!/duplicate mapping row/i.test(diagnostic.message)) {
return null;
}
return getFirstQuotedDiagnosticValue(diagnostic.message);
}
function getMissingFrontmatterKey(diagnostic: ValidationWarning): string | null {
if (!/required frontmatter/i.test(diagnostic.message)) {
return null;
}
return getFirstQuotedDiagnosticValue(diagnostic.message);
}
function getFrontmatterExampleForDiagnostic(diagnostic: ValidationWarning): string | null {
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: ValidationWarning): string | null {
if (!/render_mode/i.test(diagnostic.message)) {
return null;
}
return getFirstQuotedDiagnosticValue(diagnostic.message);
}
function getDiagnosticMetadata(
diagnostic: ValidationWarning,
t: ModelWeaveTranslator
@ -5041,6 +5299,14 @@ function getDiagnosticStringValue(value: unknown): string | 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;
}
@ -5062,6 +5328,9 @@ function formatDiagnosticAsMarkdown(
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");
}

View file

@ -1250,6 +1250,21 @@ body.model-weave-focus-mode-active {
line-height: 1.45;
}
.model-weave-diagnostic-detail-box {
margin-top: 8px;
padding: 8px;
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
background: var(--background-secondary);
}
.model-weave-diagnostic-detail-title {
margin-bottom: 6px;
color: var(--text-muted);
font-size: var(--model-weave-font-size-small);
font-weight: 600;
}
.model-weave-diagnostic-meta-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));

View file

@ -214,6 +214,14 @@ test("preview and related-view labels respect UI language dictionaries", () => {
en("screen.preview.layoutMissing"),
"Layout is missing or undefined"
);
assert.equal(en("diagnostics.details.title"), "Diagnostic details");
assert.equal(en("diagnostics.details.expectedHeader"), "Expected header");
assert.equal(en("diagnostics.details.unresolvedReference"), "Unresolved reference");
assert.equal(en("diagnostics.copyExpectedHeader"), "Copy expected header");
assert.equal(ja("diagnostics.details.title"), "診断詳細");
assert.equal(ja("diagnostics.details.expectedHeader"), "期待されるヘッダー");
assert.equal(ja("diagnostics.details.unresolvedReference"), "未解決の参照");
assert.equal(ja("diagnostics.copyExpectedHeader"), "期待ヘッダーをコピー");
assert.equal(en("sourceLinks.title"), "Source links");
assert.equal(en("sourceLinks.path"), "Path");
assert.equal(en("sourceLinks.status"), "Status");