Ideogram + Recraft UX parity: master size toggle, last-session persistence, frontmatter auto-init, brand-template copy rewrite
Summary
Follow-up to the initial Ideogram integration (commits e17159c, 8ccf789). The core API wiring works; this round addresses the friction the user hit while actually using it: opaque brand-template fields, no "select all" affordance on size toggles, modal state forgotten between opens, and an invisible image_prompt frontmatter convention that left first-time users with no signal that the plugin uses it.
The same UI affordances were ported to the Recraft modal so both providers feel symmetrical. The two modals are now functionally identical for everything that isn't a genuine API-capability difference.
1. Master "All" size toggle (both modals)
The Image Sizes section header now carries a flush-right "All" toggle alongside the title. Click on → every configured size enables. Click off → all clear. The two-way sync is the interesting bit:
Master toggle ON updates individual size toggles via tracked
ToggleComponentreferences.Individual size toggle changes recompute "are all selected?" and call
setValue()on the master to reflect.Obsidian's
ToggleComponent.setValue()doesn't re-fireonChange(per their convention), so no infinite loops.
State tracked via two new modal fields:
private masterSizeToggle: ToggleComponent | null = null;
private sizeToggles: Map<string, ToggleComponent> = new Map(); Header layout uses inline flex (display: flex; justify-content: space-between) on the existing .image-gin-section-header div — no CSS changes, no new selectors. The Setting component for the master toggle has its .setting-item-info div removed post-render so the toggle hugs the right edge cleanly without the empty info column the Setting constructor inserts by default.
2. Last-session persistence via plugin.saveData() (both modals)
Modal-state fatigue was real — users were re-toggling banner/portrait/square and re-flipping the "Write to frontmatter" toggle on every open. Added a lastSession block to settings, restored on construct, persisted on generate.
Settings shape additions
IdeogramSessionState (six fields — Ideogram has more per-call knobs):
selectedSizes: string[];
styleType: IdeogramStyleType;
renderingSpeed: IdeogramRenderingSpeed;
magicPrompt: IdeogramMagicPrompt;
layerizeText: boolean;
writeToFrontmatter: boolean; RecraftSessionState (two fields — Recraft has no per-call style overrides; style lives in settings):
selectedSizes: string[];
writeToFrontmatter: boolean; Both nested into ImageGinSettings. IdeogramSessionState lives at ideogram.lastSession (matches existing nested provider pattern); RecraftSessionState lives at top-level recraftLastSession (matches existing flat recraft* pattern — deliberately avoiding a partial migration into recraft: { ... } per the original blueprint's out-of-scope list).
Hydration discipline
In each modal's constructor, size IDs from the persisted session are filtered against the current settings.imageSizes set before being added to selectedSizes:
const validSizeIds = new Set(this.plugin.settings.imageSizes.map(s => s.id));
for (const id of session.selectedSizes) {
if (validSizeIds.has(id)) this.selectedSizes.add(id);
} If a user deletes a size preset from settings, stale IDs are dropped silently rather than haunting the modal.
Persistence timing
Save runs at the start of handleGenerate, before the API call:
this.plugin.settings.ideogram.lastSession = { ...current modal state };
await this.plugin.saveSettings();
try { ... API call ... } Rationale: even if the API fails, the user's UI choices are remembered. Saving on every individual toggle would be wasteful (write amplification) and would pollute the file with half-baked state if the user just opens the modal and cancels.
What's intentionally NOT persisted
imagePrompt— per-file, lives in frontmatternegativePrompt(Ideogram) — derived per-open fromsettings.baseNegativePrompt + frontmatter.image_negative_prompt; persisting would surprise across filesseed(Ideogram) — also per-file, comes fromimage_seedfrontmatter
Mental model is now three-layered: settings tab = brand-wide constants, frontmatter = per-file content, lastSession = "where I left off" UI state.
3. Frontmatter image_prompt auto-creation on modal open (both modals)
Marketplace-readiness fix. Before: a user opening the modal on a fresh note saw an empty textarea with no signal that the plugin uses an image_prompt frontmatter convention. The key only appeared in the file after a successful generate, and only if the "Write to frontmatter" toggle was on. Hidden contract.
After: both modals run this sequence in their open path:
Look up
frontmatter[settings.imagePromptKey]via metadata cache.If undefined (key missing or no frontmatter at all):
processFrontMatterwrites the key with an empty value. Obsidian's emitter creates the---block if it doesn't exist.If present: read it as before.
Inside the processFrontMatter callback, a redundant if (m[key] === undefined) guard prevents a stale-cache race from clobbering a real value with "".
Recraft's loadExistingPrompt was already async, so this was a body change. Ideogram's applyFrontmatterOverrides was sync — converted to async and the onOpen call site now awaits it.
The "Write prompt to frontmatter" toggle still has a use: turn it off to test a one-off prompt without overwriting the saved value. Its scope just narrows from "controls whether the key exists" to "controls whether the textarea's current value is persisted at generate time."
This neutralizes a marketplace-reviewer pushback pattern (hidden conventions / silent no-ops) without removing user control.
4. Brand Template copy rewrite (settings)
The user reported the prefix/suffix fields were confusing — the existing one-line description didn't make the two assembly modes (bookend vs. slot insertion) explicit, and the placeholder examples didn't communicate what each field is for.
Settings → Brand Template intro
Replaced the single inline <p> with a structured explanation: a paragraph defining the per-file prompt, an ordered list with <strong>-tagged mode names ("Bookends" and "Slot insertion"), each with a concrete example and the literal prefix + per-file prompt + suffix formula in <code>. Closing pointer to the modal's Resolved Prompt Preview.
Field-level labels and descriptions
Renamed for clarity:
| Field | Old label | New label |
brandTemplate.prefix | "Prompt prefix" | "Prompt prefix — Style Notes" |
brandTemplate.suffix | "Prompt suffix" | "Prompt suffix — Brand Alignment" |
Each field's description now answers "what is this for?" first, then states the assembly rule. Placeholder examples (provided directly by the user during this session):
Prefix:
e.g. Style Notes: Comic-book editorial illustration in a clean modern style: {prompt}. Vibrant flat colors, slight halftone texture, confident inked outlines, dynamic composition.Suffix:
e.g. Brand Alignment: Include colors {list colors and hex values}, with green and blue being more background ambient colors to keep the feel aligned with brandBase negative:
e.g. no text, no watermarks, no signatures, no captions, no stock-photo aesthetic
The user's mental division (Style Notes for visual approach, Brand Alignment for hard color/motif constraints, Base negative for never-allowed content) is now embedded in the UI rather than something a reader has to infer.
5. Ideogram modal copy rewrite
To match the new mental model from §4:
Image Prompt section header: "Image Prompt (subject matter)" → "Image Prompt — Subject Matter"
Above the prompt textarea: a help paragraph saying explicitly describe ONLY scene content; style and brand colors come from settings. Distinguishes the per-file slot from the brand-wide template at the point of use.
Image prompt placeholder: replaced the short example with a fully worked one (the user's own AI-Agents scene) so the field's intent is unmistakable.
Resolved Prompt Preview section: now shows mode-aware guidance text above the preview block:
No prefix/suffix configured → "No brand template configured — your prompt is sent to Ideogram exactly as typed. Set a prefix/suffix in plugin settings to wrap it automatically."
{prompt}token in prefix → "Slot-insertion mode: your prompt is substituted into the prefix at the {prompt} token; suffix is ignored."Otherwise → "Bookend mode: prefix prepended, suffix appended (separated by spaces)."
The "no template configured" hint is the most useful — when both prefix and suffix are empty (the user's reported confusion), the preview previously just echoed the typed prompt with no explanation; now the modal tells the reader why.
Negative prompt override label and placeholder: relabeled "Negative prompt — what to exclude from this image" with a placeholder spelling out that the field is pre-merged from settings + frontmatter and is editable for one-off run additions.
6. Diagnostic logging in the size-toggle path (both modals)
While debugging the user's "I toggled banner but got back all three" report, added structured [Ideogram] / [Recraft] -prefixed log entries on every size toggle change and at the start of handleGenerate. Each log line shows which IDs are in selectedSizes post-mutation and (in handleGenerate) which IDs the loop will iterate.
Recraft's CurrentFileModal already had similar logging from a prior session — kept as-is and brought Ideogram up to the same diagnostic level. The user's bug turned out not to be in the toggle code (selection state was correct); the logging stays as load-bearing diagnostics for any future regressions in this exact failure mode.
7. Version bump to 0.1.1
Three files synced (same pattern as the prior 0.0.9 → 0.1.0 bump in 2026-05-03_01.md §5):
manifest.jsonversion:0.1.0→0.1.1package.jsonversion:0.1.0→0.1.1versions.json: added"0.1.1": "1.8.10"(kept0.0.9and0.1.0entries)
minAppVersion stayed at 1.8.10 — none of this session's work uses APIs newer than that. Bumped manually rather than via pnpm version to keep this commit's file list scoped to what actually changed.
8. Build artifacts
styles.css regenerated by pnpm build — the bundle's content is identical in intent (same source CSS in src/styles/); the diff is non-semantic esbuild output churn. Re-bundled as part of verifying every change in this session.
log.json is the runtime FileLogger output (created during testing). Appears modified because actual API requests + responses were logged during the session's manual test runs. Not a source change.
API parity matrix (after this session)
| Feature | Recraft | Ideogram | Notes |
image_prompt auto-create on modal open | ✅ | ✅ | New, both — §3 |
| Master "All" toggle in size header | ✅ | ✅ | New, both — §1 |
| Two-way master ↔ individual sync | ✅ | ✅ | New, both — §1 |
lastSession UI-state persistence | ✅ | ✅ | New, both — §2 |
| Per-call style overrides | ❌ | ✅ | Ideogram-only API capability (style_type/magic_prompt/rendering_speed are per-request) |
| Brand-template prompt wrapping | ❌ | ✅ | Ideogram-only (Recraft uses server-side style_id for the equivalent) |
| Resolved Prompt Preview | ❌ | ✅ | Ideogram-only (only meaningful when wrapping happens) |
| Layerize-text post-process | ❌ | ✅ | Ideogram-only API endpoint |
Per-file frontmatter overrides (image_negative_prompt / image_style_type / image_seed) | ❌ | ✅ | Ideogram-only |
Custom style_id / imageStylesJSON | ✅ | ❌ | Recraft-only API capability |
Asymmetries that remain are genuine API-capability differences, not UX inconsistencies.
Verified working
pnpm build— green: ESLint passes (zero warnings),tsc -noEmit -skipLibCheckpasses, esbuild producesmain.js+styles.css.Manual smoke test of Ideogram modal: master toggle flips all three size toggles; individual toggle change flips master to OFF; close + reopen restores selection; close + reopen on a no-frontmatter note creates
image_prompt: ""block; per-call override dropdowns persist across opens.Recraft modal: same master-toggle behavior; close + reopen restores selection and
writeToFrontmatterchoice.
Files touched
Modified:
src/settings/settings.ts—IdeogramSessionState+RecraftSessionStatetypes,lastSessiondefaults nested underideogram,recraftLastSessionat top level, Brand Template intro paragraph rewrite + field-level renames + placeholder updatessrc/modals/IdeogramModal.ts—ToggleComponentref tracking, master toggle in size header with two-way sync,lastSessionhydrate-on-construct + save-on-generate,applyFrontmatterOverridesmade async with auto-create of missingimage_promptkey, prompt section + preview section copy updates, negative-prompt textarea label + placeholdersrc/modals/CurrentFileModal.ts— same patterns:ToggleComponentref tracking, master size toggle,recraftLastSessionhydrate + save,loadExistingPromptupdated to auto-create missingimage_prompt
Build/runtime artifacts (not source changes):
styles.css— esbuild re-bundle (same source CSS)log.json— FileLogger output from manual testing