Release compliance¶
The community-directory submission gate. It sits parallel to the test plan
— the test plan verifies behaviour, this verifies shippability — and holds the release
requirements (the former standalone RC1–RC11, now the R1–R30 audit).
Before each community-directory submission the plugin is audited against Obsidian's three official rule sources — every rule below is drawn from one of them:
- Developer policies (DP) — the hard directory rules every plugin must obey: no ads, no telemetry, no obfuscation, no self-updating code, disclose any out-of-vault file access, and so on.
- Plugin guidelines (PG) —
the review checklist of recommended practices: use
setHeading(), sentence-case UI strings,normalizePath()user paths, no hardcoded styling, release resources on unload, and so on. - Submit your plugin +
Submission requirements
(SR) — the manifest/packaging checks the submission bot and human reviewers run:
id/namenaming,version/minAppVersion,isDesktopOnly, the release artifacts, and so on.
The plugin is accepted into Obsidian's community plugin directory: https://community.obsidian.md/plugins/live-image-editor.
Last audited 2026-06-05. There is no hard-policy violation — no ads, telemetry, obfuscation,
or self-update; an MIT LICENSE is present; no trademark clash. All review-checklist
rules (R1–R30) are now met — the former open items (R20–R30) were closed in the v0.4.2
release-compliance pass. The only remaining steps are the manual packaging actions in the
Submission checklist below.
Status: fulfilled — listed in the community directory
| Rule | Requirement | Source | Status |
|---|---|---|---|
| R1 | No ads (dynamic or static) | DP | |
| R2 | No client-side telemetry; no network calls in the production build | DP | |
| R3 | No self-update mechanism | DP | |
| R4 | LICENSE present and declared |
SR | |
| R5 | No innerHTML / outerHTML / insertAdjacentHTML |
PG | |
| R6 | No var (only const / let) |
PG | |
| R7 | No global app / window.app (uses this.app) |
PG | |
| R8 | No deprecated workspace.activeLeaf |
PG | |
| R9 | async/await over raw Promise chains |
PG | |
| R10 | onunload does not detach leaves |
PG | |
| R11 | No default hotkeys; checkCallback used correctly |
PG | |
| R12 | console.* only in the dev-only bridge |
PG | |
| R13 | registerEvent / registerMarkdownPostProcessor for auto-cleanup |
PG | |
| R14 | Manifest id / name follow the naming rules |
SR | |
| R15 | fundingUrl omitted (no donations) |
SR | |
| R16 | Command IDs not prefixed with the plugin id | PG | |
| R17 | version is x.y.z; minAppVersion set |
SR | |
| R18 | Template sample code removed | PG | |
| R19 | Toolbar / UI strings sentence case | PG | |
| R20 | Disclose out-of-vault file writes in this README | DP | |
| R21 | Reconcile isDesktopOnly: false with the Electron/Node usage |
SR | |
| R22 | Remove the plugin-name top-level heading in settings | PG | |
| R23 | Use setHeading() instead of raw HTML headings |
PG | |
| R24 | Sentence-case the command names (and route via i18n) | PG | |
| R25 | Sentence-case the remaining UI headings | PG | |
| R26 | Run user / constructed paths through normalizePath() |
PG | |
| R27 | Polish the manifest description (no em-dash; action verb) | SR | |
| R28 | Move static inline styles to CSS classes | PG | |
| R29 | Prefer the Vault API over the Adapter API where a TFile exists |
PG | |
| R30 | Verify listener teardown (or switch to registerDomEvent) |
PG | |
| W1 | :has() in CSS — architecturally required |
Review | |
| W2 | !important in CSS — overrides core / gated rules |
Review | |
| W3 | setWarning() / display() deprecations — 1.12.7 floor |
Review | |
| W4 | document → activeDocument in the runtime — off-Obsidian, false positive |
Review | |
| W5 | Vault enumeration (vault.getFiles / getMarkdownFiles) — F26 picker |
Review | |
| W6 | net import in the dev-only bridge — tree-shaken, false positive |
Review | |
| W7 | raw instanceof in the runtime bundle — off-Obsidian, false positive |
Review | |
| W8 | lie-runtime.js extra release asset — intentional standalone runtime, ignored by Obsidian |
Review |
Rows W1–W8 are the warnings/remarks the automated review still surfaces (and that may show on the directory page). None fails the review; each links to its one-sentence justification in Automated plugin-review pass below, with the full detail in the review reports. = kept by deliberate decision · = false positive (not in the shipped plugin).
Fulfilled rules in detail¶
- R1 — No ads (DP). The UI shows no advertisements, sponsored content, or dynamically loaded promotions.
- R2 — No telemetry, no network (DP). The production bundle makes no network requests —
fetch,requestUrl, andXMLHttpRequestappear zero times — and collects or sends nothing about usage. - R3 — No self-update (DP). Nothing downloads or updates the plugin at runtime; the only socket server (the CDP debug relay) is dev-only, gated behind
__LIE_DEV__, and tree-shaken out of the shippedmain.js(verified: 0 hits). - R4 — License (SR). An MIT
LICENSEis present and declared. - R5 — No raw HTML injection (PG). The DOM is built with Obsidian's
createEl/createDivhelpers; noinnerHTML/outerHTML/insertAdjacentHTML. - R6 — No
var(PG). Onlyconst/let. - R7 — No global
app(PG). The plugin always usesthis.app, never the deprecated globalapp/window.app. - R8 — No
activeLeaf(PG). Avoids the deprecatedworkspace.activeLeaf. - R9 — async/await (PG). Asynchronous flows use
async/await, not raw Promise chains. - R10 — Clean
onunload(PG).onunloaddoes not detach the plugin's leaves (Obsidian manages that). - R11 — No default hotkeys (PG). Commands register no default hotkeys and use
checkCallback, so they only appear when an image is actionable. - R12 — Logging only in dev (PG).
console.*calls live solely in the dev-only bridge, never in production paths. - R13 — Auto-cleanup (PG). Events and the markdown post-processor register through
registerEvent/registerMarkdownPostProcessor, so Obsidian releases them on unload. - R14 — Manifest naming (SR).
idislive-image-editor(lowercase-dashes, no "obsidian"/"plugin");nameisLive Image Editor(no "Obsidian"/"Plugin"). - R15 — No funding solicitation (SR).
fundingUrlis omitted; the plugin asks for no donations. - R16 — Command IDs (PG). Command IDs are not prefixed with the plugin id (Obsidian adds the prefix itself).
- R17 — Versioning (SR).
versionis semverx.y.zandminAppVersionis set in the manifest. - R18 — No sample code (PG). The template scaffolding (
MyPlugin/SampleSettingTab/SampleModal) is removed. - R19 — Sentence case (PG). Toolbar and panel strings are already sentence case.
- R20 — Out-of-vault file writes disclosed (DP). See File system access & platform support below — the export save dialog can write the rendered image anywhere the user chooses, including outside the vault.
- R21 —
isDesktopOnly: falsereconciled (SR). The Electron/Node access (export save dialog, macOS rotate gesture) is dynamic + feature-detected with mobile fallbacks, so the plugin genuinely runs on mobile;falseis kept and the degradation is documented (code comment inexport.ts+ the platform note below). - R22 — No plugin-name heading (PG; Bug 68). The top-level
<h2>plugin-name heading in the settings tab was removed. - R23 —
setHeading()(PG; Bug 69). Section headings usenew Setting(...).setHeading()instead of raw<h3>elements. - R24 — Sentence-case commands (PG; Bug 70). The size/align command names are sentence case (
Size: small,Align: left, …) and routed through i18n. - R25 — Sentence-case headings (PG; Bug 71). The remaining UI headings (
CSS snippets,Editing toolbar integration) are sentence case. - R26 —
normalizePath()(PG; Bug 72). User-entered (export fallback) and constructed (snippet) paths pass throughnormalizePath(). - R27 — Manifest description (SR). Leads with an action verb and contains no em-dash or special characters.
- R28 — No hardcoded styling (PG). The one static inline style (the version-warning colour) moved to a
.lie-settings-warningCSS class; only runtime-computed geometry remains inline. - R29 — Vault API (PG).
suggestExportPathusesVault.getAbstractFileByPath()rather than the adapter; the unavoidableconfigDir/snippetsadapter calls (config-dir files are not vaultTFiles) are left as-is. - R30 — Listener teardown (PG). Every direct
document/windowlistener is interaction-scoped with matching teardown (popup close, crop exit, drag end, submenu/toolbar detach,{ once: true }); plugin-lifetime listeners already useregisterDomEvent.registerDomEventis intentionally not used for the per-interaction listeners (it holds until unload, so it cannot detach a per-dragpointermove).
Automated plugin-review pass (v0.6.x)¶
Obsidian's automated submission review — the eslint-plugin-obsidianmd recommended ruleset, a CSS
scan for :has / !important, and a behaviour scan — is reproduced locally as separate, dev-only
passes: npm run lint:obsidian (a dedicated eslint.obsidian.config.mjs) and npm run lint:css
(stylelint). The shipped linter (npm run lint / eslint.config.mjs) is kept exactly as-is
(requirement T9), so reproducing the review never touches the gate. lint:obsidian reports 0
errors (only the documented warnings below remain).
The official review now passes (0 errors). Everything it still surfaces is a non-failing
warning ( — kept by deliberate decision) or a false
positive ( — never reaches the shipped plugin). Each is justified below;
full per-item, line-referenced detail is in the review reports — latest
review-0.6.10.md (commit 5aae54e); earlier
review-0.6.9.md, review-0.6.8.md,
review-0.6.5.md, review-0.6.2.md; the
original failing pass in review-0.6.0.md /
review-0.6.1.md).
-
:has()(CSS scan) — only used where unavoidable (the runtime itself is:has-free; target is Electron/Chromium, full support): - the reveal slaving
.cm-line:has(> .cm-formatting)reacts to Obsidian's own editor DOM; - the alignment-float host rules
.host:has(.lie-image-area.lie-…)style a flow-participant host the plugin does not own, reacting to the box's marker. -
!important(CSS scan) — each beats an Obsidian-core or higher-specificity rule (removing them would rely on fragile cascade order): .lie-frame > img { max-width:none }beats the theme'simg { max-width };- the crop-frame + handle suppression (
.lie-cropping), the dismissed/native reveal and the tall-float cap each beat their higher-specificity counterparts. -
setWarning()/display()deprecations (@typescript-eslint/no-deprecated) — their replacements (setDestructive/getSettingDefinitions) are@since 1.13.0, butminAppVersionis 1.12.7, wheredisplay()is the sanctioned fallback. Kept until the floor is raised. -
document→activeDocument(obsidianmd/prefer-active-doc) — the in-Obsidian plugin source was converted in Change 40 (0.6.9); every remaining hit is inruntime.ts, the framework-free off-Obsidian bundle (compiled tolie-runtime.js, never part of the shippedmain.js), where Obsidian'sactiveDocumentglobal does not exist — the same exception class as its rawinstanceof. (The runtime itself SHIMSactiveDocument/activeWindowfor the shared core it bundles — Bug 119, [0.6.13] — which adds a couple of baredocument/windowhits on the shim line, the same off-Obsidian false-positive class. The separate in-pluginwindow→activeWindowconversion stays open under Feature 39, not currently surfaced by the review.) - Vault enumeration (
vault.getFiles/getMarkdownFiles) — the F26 Replace-image picker needs the full image-candidate set up front; already filtered to image extensions; no narrower public API. -
netimport (dev-bridge.ts) — the dev-only CDP relay is gated behind__LIE_DEV__and tree-shaken out of production (0 hits). Never ships. - Raw
instanceof(runtime.ts) — the framework-free off-Obsidian bundle imports noobsidian, so the.instanceOf()helper does not exist there; rawinstanceofagainst standard DOM is correct. -
lie-runtime.jsextra release asset (Releases scan) — the GitHub release also attaches the standalone runtime (lie-runtime.js) for off-Obsidian use; Obsidian's installer downloads onlymain.js/manifest.json/styles.css, so it ignores this asset. Deliberate — the runtime ships via the release, not the plugin.
Submission checklist (manual, at release time)¶
Not code — the packaging steps performed when cutting a community-directory release:
- GitHub release with
main.js+manifest.json(+styles.css) attached as binaries. - Release tag == manifest
version, with novprefix (tag0.4.0, notv0.4.0). - Manifest at HEAD of the default branch is the submitted one (
main). - PR to
obsidianmd/obsidian-releaseseditingcommunity-plugins.json, one plugin per PR. - GitHub account linked to the Obsidian profile; agree to the Developer policies in the form.