Enhance profile selection and interview wizard functionality
Some checks failed
Node.js build / build (20.x) (push) Has been cancelled
Node.js build / build (22.x) (push) Has been cancelled

- Introduced preferred note types in ProfileSelectionModal to prioritize user selections, improving the user experience during profile selection.
- Updated InterviewWizardModal to accept initial pending edge assignments, allowing for better state management and user feedback.
- Added a new action, `create_section_in_note`, to the todo generation process, expanding the capabilities of the interview workflow.
- Enhanced the startWizardAfterCreate function to support initial pending edge assignments, streamlining the wizard initiation process.
- Improved CSS styles for preferred profiles, enhancing visual distinction in the profile selection interface.
This commit is contained in:
Lars 2026-02-07 21:22:35 +01:00
parent 7627a05af4
commit 0a346d3886
9 changed files with 650 additions and 74 deletions

View File

@ -643,6 +643,7 @@ export class ChainWorkbenchModal extends Modal {
const labels: Record<string, string> = {
link_existing: "Link Existing",
create_note_via_interview: "Create Note",
create_section_in_note: "Create Section",
insert_edge_forward: "Insert Edge",
insert_edge_inverse: "Insert Inverse",
choose_target_anchor: "Choose Anchor",
@ -867,6 +868,45 @@ export class ChainWorkbenchModal extends Modal {
}
break;
case "create_section_in_note":
if (todo.type === "missing_slot") {
const { createSectionInNote } = await import("../workbench/createSectionAction");
// Find the match for this todo
const matchForTodo = this.model.matches.find((m) =>
m.todos.some((t) => t.id === todo.id)
);
if (!matchForTodo) {
new Notice("Could not find match for todo");
return;
}
// Get edge vocabulary
const { VocabularyLoader } = await import("../vocab/VocabularyLoader");
const { parseEdgeVocabulary } = await import("../vocab/parseEdgeVocabulary");
const vocabText = await VocabularyLoader.loadText(this.app, this.settings.edgeVocabularyPath);
const edgeVocabulary = parseEdgeVocabulary(vocabText);
await createSectionInNote(
this.app,
activeEditor ?? null,
activeFile ?? null,
todo,
this.vocabulary,
edgeVocabulary,
this.settings,
matchForTodo.templateName,
matchForTodo,
this.chainTemplates,
this.chainRoles
);
// Workbench mit neuen Daten aktualisieren (wie beim Anlegen von Edges)
await this.refreshWorkbench();
}
break;
default:
new Notice(`Action "${action}" not yet implemented`);
}

View File

@ -93,7 +93,8 @@ export class InterviewWizardModal extends Modal {
onSubmit: (result: WizardResult) => void,
onSaveAndExit: (result: WizardResult) => void,
settings?: MindnetSettings,
plugin?: { ensureGraphSchemaLoaded?: () => Promise<import("../mapping/graphSchema").GraphSchema | null> }
plugin?: { ensureGraphSchemaLoaded?: () => Promise<import("../mapping/graphSchema").GraphSchema | null> },
initialPendingEdgeAssignments?: PendingEdgeAssignment[]
) {
super(app);
@ -117,6 +118,7 @@ export class InterviewWizardModal extends Modal {
edgingMode: profile.edging?.mode || "none",
edging: profile.edging, // Full edging object for debugging
profileKeys: Object.keys(profile), // All keys in profile
initialPendingEdgeAssignments: initialPendingEdgeAssignments?.length || 0,
});
// Validate steps - only throw if profile.steps is actually empty
@ -130,6 +132,12 @@ export class InterviewWizardModal extends Modal {
this.state = createWizardState(profile);
// Inject initial pending edge assignments if provided
if (initialPendingEdgeAssignments && initialPendingEdgeAssignments.length > 0) {
this.state.pendingEdgeAssignments = [...initialPendingEdgeAssignments];
console.log("[InterviewWizardModal] Injected initial pending edge assignments:", initialPendingEdgeAssignments.length);
}
// Initialize note index for entity picker
this.noteIndex = new NoteIndex(this.app);

View File

@ -16,19 +16,22 @@ export class ProfileSelectionModal extends Modal {
initialTitle: string;
defaultFolderPath: string;
private noteIndex: NoteIndex;
preferredNoteTypes: string[] = []; // Note types to mark as preferred
constructor(
app: App,
config: InterviewConfig,
onSubmit: (result: ProfileSelectionResult) => void,
initialTitle: string = "",
defaultFolderPath: string = ""
defaultFolderPath: string = "",
preferredNoteTypes: string[] = []
) {
super(app);
this.config = config;
this.onSubmit = onSubmit;
this.initialTitle = initialTitle;
this.defaultFolderPath = defaultFolderPath;
this.preferredNoteTypes = preferredNoteTypes;
this.noteIndex = new NoteIndex(app);
}
@ -106,41 +109,75 @@ export class ProfileSelectionModal extends Modal {
}
};
// Sort profiles: preferred first, then others
const sortProfiles = (profiles: InterviewProfile[]): InterviewProfile[] => {
return [...profiles].sort((a, b) => {
const aPreferred = this.preferredNoteTypes.includes(a.note_type);
const bPreferred = this.preferredNoteTypes.includes(b.note_type);
if (aPreferred && !bPreferred) return -1;
if (!aPreferred && bPreferred) return 1;
return 0;
});
};
// Profile selection (grouped)
for (const [groupName, profiles] of grouped.entries()) {
contentEl.createEl("h3", { text: groupName });
for (const profile of profiles) {
const sortedProfiles = sortProfiles(profiles);
let firstPreferredSelected = false;
for (const profile of sortedProfiles) {
const isPreferred = this.preferredNoteTypes.includes(profile.note_type);
const setting = new Setting(contentEl)
.setName(profile.label)
.setDesc(`Type: ${profile.note_type}`)
.addButton((button) => {
button.setButtonText("Select").onClick(() => {
// Clear previous selection
contentEl.querySelectorAll(".profile-selected").forEach((el) => {
if (el instanceof HTMLElement) {
el.removeClass("profile-selected");
}
});
// Mark as selected
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
.setDesc(`Type: ${profile.note_type}${isPreferred ? " (empfohlen)" : ""}`);
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
// Auto-select first preferred profile if none selected yet
if (isPreferred && !selectedProfile && !firstPreferredSelected) {
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
firstPreferredSelected = true;
// Log selection
console.log("Profile selected", {
key: profile.key,
label: profile.label,
noteType: profile.note_type,
stepCount: profile.steps?.length || 0,
defaultFolder: profileFolder,
});
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
}
// Highlight preferred profiles
if (isPreferred) {
setting.settingEl.addClass("profile-preferred");
}
setting.addButton((button) => {
button.setButtonText("Select").onClick(() => {
// Clear previous selection
contentEl.querySelectorAll(".profile-selected").forEach((el) => {
if (el instanceof HTMLElement) {
el.removeClass("profile-selected");
}
});
// Mark as selected
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
// Log selection
console.log("Profile selected", {
key: profile.key,
label: profile.label,
noteType: profile.note_type,
stepCount: profile.steps?.length || 0,
defaultFolder: profileFolder,
isPreferred,
});
});
});
}
}
@ -150,37 +187,60 @@ export class ProfileSelectionModal extends Modal {
contentEl.createEl("h3", { text: "Other" });
}
for (const profile of ungrouped) {
const sortedUngrouped = sortProfiles(ungrouped);
let firstPreferredSelectedUngrouped = false;
for (const profile of sortedUngrouped) {
const isPreferred = this.preferredNoteTypes.includes(profile.note_type);
const setting = new Setting(contentEl)
.setName(profile.label)
.setDesc(`Type: ${profile.note_type}`)
.addButton((button) => {
button.setButtonText("Select").onClick(() => {
// Clear previous selection
contentEl.querySelectorAll(".profile-selected").forEach((el) => {
if (el instanceof HTMLElement) {
el.removeClass("profile-selected");
}
});
// Mark as selected
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
.setDesc(`Type: ${profile.note_type}${isPreferred ? " (empfohlen)" : ""}`);
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
// Auto-select first preferred profile if none selected yet
if (isPreferred && !selectedProfile && !firstPreferredSelectedUngrouped) {
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
firstPreferredSelectedUngrouped = true;
// Log selection
console.log("Profile selected", {
key: profile.key,
label: profile.label,
noteType: profile.note_type,
stepCount: profile.steps?.length || 0,
defaultFolder: profileFolder,
});
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
}
// Highlight preferred profiles
if (isPreferred) {
setting.settingEl.addClass("profile-preferred");
}
setting.addButton((button) => {
button.setButtonText("Select").onClick(() => {
// Clear previous selection
contentEl.querySelectorAll(".profile-selected").forEach((el) => {
if (el instanceof HTMLElement) {
el.removeClass("profile-selected");
}
});
// Mark as selected
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
// Log selection
console.log("Profile selected", {
key: profile.key,
label: profile.label,
noteType: profile.note_type,
stepCount: profile.steps?.length || 0,
defaultFolder: profileFolder,
isPreferred,
});
});
});
}
}

View File

@ -48,7 +48,8 @@ export async function startWizardAfterCreate(
isUnresolvedClick: boolean,
onWizardComplete: (result: any) => void,
onWizardSave: (result: any) => void,
pluginInstance?: { ensureGraphSchemaLoaded?: () => Promise<import("../mapping/graphSchema").GraphSchema | null> }
pluginInstance?: { ensureGraphSchemaLoaded?: () => Promise<import("../mapping/graphSchema").GraphSchema | null> },
initialPendingEdgeAssignments?: import("../interview/wizardState").PendingEdgeAssignment[]
): Promise<void> {
// Determine if wizard should start
const shouldStartInterview = isUnresolvedClick
@ -109,7 +110,8 @@ export async function startWizardAfterCreate(
onWizardComplete,
onWizardSave,
settings, // Pass settings for post-run edging
pluginInstance // Pass plugin instance for graph schema loading
pluginInstance, // Pass plugin instance for graph schema loading
initialPendingEdgeAssignments // Pass initial edge assignments
).open();
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);

View File

@ -0,0 +1,462 @@
/**
* Create section action for workbench missing slot todos.
*/
import { Notice, TFile, Modal, Setting } from "obsidian";
import type { App, Editor } from "obsidian";
import type { MissingSlotTodo } from "./types";
import type { Vocabulary } from "../vocab/Vocabulary";
import type { EdgeVocabulary } from "../vocab/types";
import type { MindnetSettings } from "../settings";
import type { ChainTemplate, ChainRolesConfig, ChainTemplatesConfig } from "../dictionary/types";
import type { TemplateMatch } from "../analysis/chainInspector";
import { EntityPickerModal } from "../ui/EntityPickerModal";
import { NoteIndex } from "../entityPicker/noteIndex";
/** Common section/node types for dropdown when slot has no allowed_node_types. */
const FALLBACK_SECTION_TYPES = [
"experience",
"insight",
"decision",
"learning",
"value",
"trigger",
"outcome",
"other",
];
export interface CreateSectionResult {
targetFilePath: string;
heading: string;
blockId: string | null;
sectionType: string | null;
sectionBody: string;
initialEdges: Array<{
edgeType: string;
targetNote: string;
targetHeading: string | null;
}>;
}
/**
* Create a new section in the chosen note.
*/
export async function createSectionInNote(
app: App,
editor: Editor | null,
activeFile: TFile | null,
todo: MissingSlotTodo,
vocabulary: Vocabulary,
edgeVocabulary: EdgeVocabulary | null,
settings: MindnetSettings,
matchTemplateName: string,
match: TemplateMatch,
chainTemplates: ChainTemplatesConfig | null,
chainRoles: ChainRolesConfig | null
): Promise<void> {
const debugLogging = settings.debugLogging;
if (!chainTemplates) {
new Notice("Chain templates not available");
return;
}
const template = chainTemplates.templates?.find((t) => t.name === matchTemplateName);
if (!template) {
new Notice("Template not found");
return;
}
const requiredEdges = getRequiredEdgesForSlot(
template,
todo.slotId,
match,
chainRoles,
vocabulary,
edgeVocabulary
);
// Chain notes: unique file paths from slot assignments
const chainNotePaths = getChainNotePaths(match);
const defaultSectionType = getDefaultSectionType(template, todo);
const sectionTypeOptions = getSectionTypeOptions(todo, template);
const result = await showCreateSectionModal(
app,
todo,
requiredEdges,
chainNotePaths,
activeFile?.path ?? null,
defaultSectionType,
sectionTypeOptions,
vocabulary,
edgeVocabulary,
settings
);
if (!result) {
return;
}
// Build section content
let sectionContent = `## ${result.heading}`;
if (result.blockId) {
sectionContent += ` ^${result.blockId}`;
}
sectionContent += "\n\n";
if (result.sectionType) {
sectionContent += `> [!section] ${result.sectionType}\n\n`;
}
if (result.initialEdges.length > 0) {
const wrapperType = settings.mappingWrapperCalloutType || "abstract";
const wrapperTitle = settings.mappingWrapperTitle || "";
const wrapperFolded = settings.mappingWrapperFolded || false;
const foldMarker = wrapperFolded ? "-" : "+";
sectionContent += `> [!${wrapperType}]${foldMarker}${wrapperTitle ? ` ${wrapperTitle}` : ""}\n`;
for (const edge of result.initialEdges) {
const targetLink = edge.targetHeading
? `${edge.targetNote}#${edge.targetHeading}`
: edge.targetNote;
sectionContent += `>> [!edge] ${edge.edgeType}\n>> [[${targetLink}]]\n`;
}
sectionContent += "\n";
}
if (result.sectionBody.trim()) {
sectionContent += result.sectionBody.trim() + "\n\n";
}
// Resolve target file
const targetFile = app.vault.getAbstractFileByPath(result.targetFilePath);
if (!targetFile || !(targetFile instanceof TFile)) {
new Notice("Zieldatei nicht gefunden");
return;
}
const content = await app.vault.read(targetFile);
const needsNewline = content.length > 0 && !content.endsWith("\n");
const sectionToInsert = (needsNewline ? "\n" : "") + sectionContent;
await app.vault.modify(targetFile, content + sectionToInsert);
// Die Rückwärtskanten stehen in der neuen Sektion (initialEdges sind bereits Inverse-Typen und zeigen auf Quell-Notes).
// Kein separater Eintrag in anderen Notes nötig.
new Notice(`Sektion "${result.heading}" in ${targetFile.basename} erstellt`);
if (debugLogging) {
console.log("[createSectionInNote] Section created:", result.heading, "in", result.targetFilePath);
}
}
function getChainNotePaths(match: TemplateMatch): string[] {
const paths = new Set<string>();
for (const a of Object.values(match.slotAssignments)) {
if (a?.file) {
paths.add(a.file);
}
}
return Array.from(paths).sort();
}
function getDefaultSectionType(template: ChainTemplate, todo: MissingSlotTodo): string {
const slot = template.slots.find((s) => (typeof s === "string" ? s === todo.slotId : s.id === todo.slotId));
if (typeof slot !== "string" && slot?.allowed_node_types?.length) {
const first = slot.allowed_node_types[0];
return first != null ? first : FALLBACK_SECTION_TYPES[0]!;
}
const slotId = todo.slotId.toLowerCase();
if (FALLBACK_SECTION_TYPES.includes(slotId)) {
return slotId;
}
return todo.allowedNodeTypes?.[0] ?? FALLBACK_SECTION_TYPES[0]!;
}
function getSectionTypeOptions(todo: MissingSlotTodo, template: ChainTemplate): string[] {
const slot = template.slots.find((s) => (typeof s === "string" ? s === todo.slotId : s.id === todo.slotId));
if (typeof slot !== "string" && slot?.allowed_node_types?.length) {
return [...slot.allowed_node_types];
}
if (todo.allowedNodeTypes?.length) {
return [...todo.allowedNodeTypes];
}
return [...FALLBACK_SECTION_TYPES];
}
async function showCreateSectionModal(
app: App,
todo: MissingSlotTodo,
requiredEdges: Array<{
edgeType: string;
targetNote: string;
targetHeading: string | null;
suggestedAlias?: string;
}>,
chainNotePaths: string[],
activeFilePath: string | null,
defaultSectionType: string,
sectionTypeOptions: string[],
vocabulary: Vocabulary,
edgeVocabulary: EdgeVocabulary | null,
settings: MindnetSettings
): Promise<CreateSectionResult | null> {
return new Promise((resolve) => {
const noteIndex = new NoteIndex(app);
class CreateSectionModal extends Modal {
result: CreateSectionResult | null = null;
heading = "";
blockId = "";
sectionType = defaultSectionType;
sectionBody = "";
targetFilePath: string =
(activeFilePath && chainNotePaths.includes(activeFilePath)
? activeFilePath
: chainNotePaths[0] ?? activeFilePath ?? "") as string;
initialEdges: CreateSectionResult["initialEdges"] = [];
constructor() {
super(app);
}
onOpen(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl("h2", { text: "Neue Sektion erstellen" });
// Target note dropdown: chain notes first, then "Other"
const chainNoteOptions = chainNotePaths.map((p) => ({ value: p, label: p.split("/").pop() ?? p }));
const hasChainNotes = chainNoteOptions.length > 0;
if (!this.targetFilePath && hasChainNotes && chainNoteOptions[0]) {
this.targetFilePath = chainNoteOptions[0].value;
}
new Setting(contentEl)
.setName("Ziel-Note")
.setDesc("Note, in der die Sektion angelegt wird (primär: Notes aus der Chain)")
.addDropdown((dropdown) => {
for (const { value, label } of chainNoteOptions) {
dropdown.addOption(value, label);
}
dropdown.addOption("__other__", "… Andere Note wählen");
dropdown.setValue(
this.targetFilePath && (chainNotePaths.includes(this.targetFilePath) || !hasChainNotes)
? this.targetFilePath
: "__other__"
);
dropdown.onChange((value) => {
if (value === "__other__") {
this.chooseOtherNote(dropdown);
} else {
this.targetFilePath = value;
}
});
});
// Heading
new Setting(contentEl)
.setName("Überschrift")
.setDesc("Überschrift der neuen Sektion")
.addText((text) => {
text.setValue("").onChange((value) => {
this.heading = value;
});
text.inputEl.focus();
});
// Section type dropdown
new Setting(contentEl)
.setName("Sektionstyp")
.setDesc("Typ der Sektion (aus Slot/Vorlage)")
.addDropdown((dropdown) => {
for (const opt of sectionTypeOptions) {
dropdown.addOption(opt, opt);
}
dropdown.setValue(this.sectionType);
dropdown.onChange((value) => {
this.sectionType = value;
});
});
// Block ID (optional)
new Setting(contentEl)
.setName("Block-ID (optional)")
.setDesc("z. B. learning, insight-1")
.addText((text) => {
text.setValue("").onChange((value) => {
this.blockId = value.trim();
});
});
// Initial edges (read-only info, all applied)
if (requiredEdges.length > 0) {
contentEl.createEl("h3", { text: "Verbindungen (werden angelegt)" });
const ul = contentEl.createEl("ul", { cls: "create-section-edges-list" });
for (const edge of requiredEdges) {
const li = ul.createEl("li");
li.textContent = `${edge.suggestedAlias || edge.edgeType}${edge.targetNote}${edge.targetHeading ? `#${edge.targetHeading}` : ""}`;
}
this.initialEdges = requiredEdges.map((e) => ({
edgeType: e.suggestedAlias || e.edgeType,
targetNote: e.targetNote,
targetHeading: e.targetHeading,
}));
}
// Section body text
const bodyDesc = contentEl.createEl("div", { cls: "setting-item-description" });
bodyDesc.setText("Inhalt der Sektion (optional). Wird unter den Verbindungen eingefügt.");
const bodyContainer = contentEl.createDiv({ cls: "setting-item" });
const control = bodyContainer.createDiv({ cls: "setting-item-control" });
const textarea = control.createEl("textarea", {
attr: { placeholder: "Text für die neue Sektion…", rows: "5" },
});
textarea.style.width = "100%";
textarea.oninput = () => {
this.sectionBody = textarea.value;
};
// Buttons
new Setting(contentEl).addButton((button) => {
button.setButtonText("Abbrechen").onClick(() => {
resolve(null);
this.close();
});
}).addButton((button) => {
button
.setButtonText("Sektion erstellen")
.setCta()
.onClick(() => {
if (!this.heading.trim()) {
new Notice("Bitte eine Überschrift eingeben");
return;
}
if (!this.targetFilePath) {
new Notice("Bitte eine Ziel-Note wählen");
return;
}
this.result = {
targetFilePath: this.targetFilePath,
heading: this.heading.trim(),
blockId: this.blockId || null,
sectionType: this.sectionType || null,
sectionBody: this.sectionBody,
initialEdges: this.initialEdges,
};
resolve(this.result);
this.close();
});
});
}
private chooseOtherNote(dropdown: { setValue: (v: string) => void; selectEl: HTMLSelectElement }): void {
const picker = new EntityPickerModal(app, noteIndex, (result) => {
const path = result.path.endsWith(".md") ? result.path : `${result.path}.md`;
this.targetFilePath = path;
const opt = dropdown.selectEl.querySelector(`option[value="${path}"]`);
if (!opt) {
const optOther = dropdown.selectEl.querySelector('option[value="__other__"]');
const newOpt = document.createElement("option");
newOpt.value = path;
newOpt.textContent = result.basename;
dropdown.selectEl.insertBefore(newOpt, optOther ?? null);
}
dropdown.setValue(path);
picker.close();
});
picker.open();
}
onClose(): void {
this.contentEl.empty();
}
}
const modal = new CreateSectionModal();
modal.open();
});
}
function getRequiredEdgesForSlot(
template: ChainTemplate,
slotId: string,
match: TemplateMatch,
chainRoles: ChainRolesConfig | null,
vocabulary: Vocabulary,
edgeVocabulary: EdgeVocabulary | null
): Array<{
edgeType: string;
targetNote: string;
targetHeading: string | null;
suggestedAlias?: string;
}> {
const requiredEdges: Array<{
edgeType: string;
targetNote: string;
targetHeading: string | null;
suggestedAlias?: string;
}> = [];
const normalizedLinks = template.links || [];
for (const link of normalizedLinks) {
const suggestedEdgeTypes: string[] = [];
if (chainRoles?.roles && link.allowed_edge_roles) {
for (const roleName of link.allowed_edge_roles) {
const role = chainRoles.roles[roleName];
if (role?.edge_types) {
suggestedEdgeTypes.push(...role.edge_types);
}
}
}
const forwardEdgeType = suggestedEdgeTypes[0] || "related_to";
// Links, die ZU unserem Slot zeigen: In der neuen Sektion Rückwärtskante (Inverse) zur Quell-Note
if (link.to === slotId) {
const sourceAssignment = match.slotAssignments[link.from];
if (!sourceAssignment) continue;
const canonical = vocabulary.getCanonical(forwardEdgeType);
const inverseType = canonical ? vocabulary.getInverse(canonical) : null;
const edgeTypeForSection = inverseType ?? forwardEdgeType;
let suggestedAlias: string | undefined;
if (edgeVocabulary && edgeTypeForSection) {
const entry = edgeVocabulary.byCanonical.get(edgeTypeForSection);
if (entry?.aliases?.length) {
suggestedAlias = entry.aliases[0];
}
}
requiredEdges.push({
edgeType: edgeTypeForSection,
targetNote: sourceAssignment.file.replace(/\.md$/, ""),
targetHeading: sourceAssignment.heading ?? null,
suggestedAlias,
});
continue;
}
// Links, die VON unserem Slot ausgehen: In der neuen Sektion Vorwärtskante zur Ziel-Note
if (link.from === slotId) {
const targetAssignment = match.slotAssignments[link.to];
if (!targetAssignment) continue;
let suggestedAlias: string | undefined;
if (edgeVocabulary) {
const entry = edgeVocabulary.byCanonical.get(forwardEdgeType);
if (entry?.aliases?.length) {
suggestedAlias = entry.aliases[0];
}
}
requiredEdges.push({
edgeType: forwardEdgeType,
targetNote: targetAssignment.file.replace(/\.md$/, ""),
targetHeading: targetAssignment.heading ?? null,
suggestedAlias,
});
}
}
return requiredEdges;
}

View File

@ -153,10 +153,7 @@ export async function createNoteViaInterview(
};
});
// Start wizard with payload
// Note: The wizard system doesn't directly accept pendingEdgeAssignments in startWizardAfterCreate
// We'll need to inject them into the wizard state after creation
// For now, we'll rely on soft validation after wizard completion
// Start wizard with initial pending edge assignments
await startWizardAfterCreate(
app,
settings,
@ -186,12 +183,9 @@ export async function createNoteViaInterview(
);
new Notice("Wizard saved");
},
pluginInstance
pluginInstance,
pendingEdgeAssignments // Pass initial edge assignments to wizard
);
// TODO: Inject pendingEdgeAssignments into wizard state
// This would require modifying InterviewWizardModal to accept initial pendingEdgeAssignments
// For MVP, we rely on soft validation and user can manually add edges
}
/**
@ -276,7 +270,8 @@ async function selectProfile(
app: App,
interviewConfig: InterviewConfig,
allowedProfiles: InterviewProfile[],
settings: MindnetSettings
settings: MindnetSettings,
preferredNoteTypes: string[] = []
): Promise<{ profile: InterviewProfile; title: string; folderPath: string } | null> {
return new Promise((resolve) => {
// Filter config to only allowed profiles
@ -285,7 +280,7 @@ async function selectProfile(
profiles: allowedProfiles,
};
// Use ProfileSelectionModal with filtered profiles
// Use ProfileSelectionModal with filtered profiles and preferred types
const modal = new ProfileSelectionModal(
app,
filteredConfig,
@ -297,11 +292,10 @@ async function selectProfile(
});
},
"", // Default title (user will set)
settings.defaultNotesFolder || ""
settings.defaultNotesFolder || "",
preferredNoteTypes // Pass preferred note types
);
// Filter profiles in modal (we'll need to modify ProfileSelectionModal or filter here)
// For now, we'll pass all profiles and let user choose
modal.onClose = () => {
resolve(null);
};

View File

@ -58,7 +58,7 @@ export async function generateTodos(
priority: "high",
slotId,
allowedNodeTypes,
actions: ["link_existing", "create_note_via_interview"],
actions: ["link_existing", "create_note_via_interview", "create_section_in_note"],
} as MissingSlotTodo);
}
}

View File

@ -54,7 +54,7 @@ export interface MissingSlotTodo extends WorkbenchTodo {
type: "missing_slot";
slotId: string;
allowedNodeTypes: string[];
actions: Array<"link_existing" | "create_note_via_interview">;
actions: Array<"link_existing" | "create_note_via_interview" | "create_section_in_note">;
}
/**

View File

@ -18,6 +18,16 @@ If your plugin does not need CSS, delete this file.
font-weight: 600;
}
.profile-preferred {
background-color: var(--background-modifier-hover);
border-left: 2px solid var(--text-accent);
padding-left: calc(var(--size-4-2) - 2px);
}
.profile-preferred .setting-item-name {
font-weight: 500;
}
/* Interview wizard styles */
.interview-prompt {
color: var(--text-muted);