1.3.7
title: "Changelog"
tags: [changelog, gfm-heading-links, obsidian-plugin]
description: "Release history and notable changes for the GFM Heading Links Obsidian plugin"
date_created: 2026-07-02
date_changed: 2026-07-18
author: "Lucas Galdino"
Changelog
All notable changes to the GFM Heading Links plugin are documented in this file.
The format is based on Keep a Changelog, and this project adheres to
Semantic Versioning.
1.3.1 — 2026-07-20
Note: This release consolidates the original v1.3.1 release infrastructure work and all
subsequent community review compliance fixes (previously spread across v1.3.2–1.3.6).
Those intermediate releases have been deleted — this is the definitive v1.3.1.
Added
- Release Infrastructure:
LICENSE(MIT), GitHub Actions release workflow
(.github/workflows/release.yml), branch strategy (main=production/DEBUG_ENABLED=false,
dev=development/DEBUG_ENABLED=true). - CI Artifact Attestations (
actions/attest@v4): Supply-chain provenance for release
artifacts. authorandauthorUrlpopulated inmanifest.json..gitignorehardened:data.json,*.pngexclusions, refined.github/and.agents/
patterns.- Typed Internal APIs (
src/vault-config.ts):getVaultConfig()andisWikilinksEnabled()
helpers replace inline(vault as any)casts. - Type-Safe Editor Suggest:
SuggestionValue,EditorSuggestInstance,
SuggestionDropdownIteminterfaces andunwrapDropdownItem()type guard — allany
types eliminated from the suggest pipeline.
Changed
package.jsonversion aligned withmanifest.json(both1.3.1).- All documentation updated:
docs/plan/*,docs/research/architectural-history.md,
docs/architecture.md,README.md. - Release links in
CHANGELOG.mdfixed to point tolucasgaldinos/obsidian-gfm-headers. - Production console hygiene:
console.log→console.debugin gated debug system
(src/debug.ts).console.debugis suppressed by default in browser DevTools at the
default "Info" log level. - Arrow functions for all monkeypatch assignments:
workspace.openLinkText,
workspace.trigger, andsuggest.selectSuggestionall use arrow functions with
explicit.call(workspace, ...)rebinding. .bind(workspace)at method capture:originalOpenLinkTextandoriginalTrigger
are bound at capture time via.bind(workspace), eliminatingthis-scoping warnings
from Obsidian's review linter.- Safe array initialization:
new Array<number>(n).fill(0)replacesnew Array(n).fill(0)
to avoid implicitany[]inference. window.setTimeout/window.clearTimeoutused instead of baresetTimeout/
clearTimeoutfor browser global compliance.loadData()cast asPartial<GfmSettings>instead ofas any.view.currentModecast as typed interface instead ofas any.injectVirtualBlockandselectSuggestionuse typedPosandSuggestionValueparams.
Removed
- Accidentally committed
image.png,image copy.png, anddata.json. - All
eslint-disableand@typescript-eslint/no-explicit-anydirective comments. - Unused
debugLog/DEBUG_ENABLEDimport frommain.ts. - Non-existent
styles.cssfrom CI release assets.
Fixed
this: voidlinter warnings: Original methods captured fromworkspaceare now
bound via.bind(workspace)at capture time, preventingthis-scoping errors when
the method is called detached from its object.- Review linter compliance: Addressed all warnings reported by Obsidian's automated
plugin review — noanytypes, noconsole.login production builds, no directive
suppressions, nofunction()withoutthis: void.
1.3.0 — 2026-07-17
Added
- Settings Tab: Configurable prefix and suffix characters for autocompleted links (e.g.,
§Heading¶). Dual-API support: uses declarativegetSettingDefinitions()on Obsidian 1.13.0+, falls back todisplay()on older versions. - Wikilink Alias Auto-Injection: When Obsidian is using wikilinks (
[[), selecting a heading from autocomplete now automatically appends|Original Heading— producing[[#gfm-slug|Original Heading]]instead of bare[[#gfm-slug]]. - Link Affixes: User-configurable prefix/suffix applied to heading aliases during autocomplete.
isGfmSlug()shared guard predicate ingfm-slugify.ts.transformSuggestion()pure pipeline extracting heading mutation logic fromselectSuggestiongod-function.decodeGfmSlug()andresolveTargetFile()shared resolution helpers.injectVirtualBlock()shared utility with cleanup callback andVIRTUAL_BLOCK_CLEANUP_MSconstant.normalizeSlug()insrc/link-parse.ts.
Changed
patch-workspace.tssplit by SRP intopatch-link-click.ts(applyClickPatch, async) andpatch-link-hover.ts(applyHoverPatch, sync).buildDocumentIndex()replaced O(n²) nested loop with 2-pass O(n) stack-based algorithm.link-target.tsrenamed totypes.ts— all 5 imports updated.- Variable renames for readability:
data→hoverEventPayload,value→suggestionValue,mutated→didModifySubpath. resolveGfmTarget()andresolveGfmTargetSync()unified to use shareddecodeGfmSlug()+resolveTargetFile()helpers, reducing each from ~90 to ~15 lines.- Hover handler no longer uses
require("./document-index")inlining — delegates toresolveGfmTargetSync(). - Settings tab uses declarative
getSettingDefinitions()API (Obsidian 1.13.0+) withdisplay()fallback. minAppVersionset to1.12.7for dual settings API support.DEBUG_ENABLED = trueon dev branch — set tofalsefor production builds.
Fixed
- Inverted guard bug:
isGfmSlug()condition was inverted inresolve-target.ts, causing valid GFM slugs to pass through unresolved. - Settings tab broken on Obsidian <1.13.0: Declarative-only API caused empty settings pane. Fixed with dual-API support.
- Affixes applied to wrong field: Affixes were appended to
suggestionValue.subpath(the slug) instead ofsuggestionValue.heading(the alias). - Wikilink alias not injected: Multiple iterations required; finally resolved via
plugin.app.workspace.activeEditor?.editorpost-insertion modification.
1.2.0 — 2026-07-13
Added
- Document Index Architecture: Lightweight background
Map<gfmSlug, AnchorTarget>per file, separate from Obsidian's cache. Enables O(1) slug lookups with zero risk of cache contamination. - Virtual Block Injection: Temporarily injects
#^gfm-click-{slug}blocks intocache.blocksto leverage Obsidian's native block-level scrolling and.is-flashinghighlight — works correctly for duplicate headings. - Autocomplete GFM Slug Output (
EditorSuggest.selectSuggestion): Selecting a heading from the[[dropdown now outputs the GFM slug (e.g.,[My Heading](file.md#my-heading)) while preserving the original heading text as the alias. - Duplicate Heading Resolution in Autocomplete: Advanced dropdown occurrence-index matching correctly resolves which Nth duplicate was selected, producing the correct collision suffix (
-1,-2, etc.). - HTML Anchor Scanning (
scanHtmlAnchors): Parses<a id="...">and<a name="...">tags for target identification (click navigation in Reading mode). - Structured Debug Logging (
src/debug.ts): 15 event types withDEBUG_ENABLEDtoggle for development instrumentation. - Unit Tests (Vitest, 17 tests):
gfmSlugify(basic, Unicode, edge cases),buildDocumentIndex(collision suffixes, endLine),resolveGfmTarget(guard logic).
Changed
- Architectural Shift: Abandoned the speculative
Heading#Headingsubpath navigation. Replaced with Document Index + Virtual Block Injection across all three monkeypatches (openLinkText,trigger('hover-link'),EditorSuggest.selectSuggestion). - Same-file navigation: Bypassed native resolution for same-file links to eliminate jump-to-top flicker.
- HTML in heading aliases: Autocomplete now strips HTML tags from heading text (e.g.,
## Title <a id="x"></a>→ aliasTitle).
Fixed
- Hover Preview on duplicate headings: Corrected
buildDocumentIndex(cache.headings)→buildDocumentIndex(cache)— the former produced an empty index, causing all hover previews to fail. - Alias loss in autocomplete: Stopped mutating
value.heading— onlyvalue.subpathis rewritten to the GFM slug. .is-flashingon duplicates: Native block navigation handles section highlighting correctly, no longer highlights wrong heading.
Known Limitations
- HTML anchor click navigation works only in Reading mode (Live Preview / Source mode under investigation — see Bug 10).
- HTML anchor hover preview is not yet supported (hover interceptor only handles heading-type targets — see Bug 6).
- GFM collision suffix uses last-write-wins (
Map.set()overwrites). First-occurrence priority deferred to v2 (see Bug 8). DEBUG_ENABLED = trueon dev branch — set tofalsefor production builds.
1.1.0 — 2026-07-03
Changed
- Architectural Overhaul: Completely abandoned the fragile CodeMirror 6
ViewPluginand DOMMutationObserverarchitecture. The plugin now globally monkeypatches Obsidian's coreWorkspacerouter (workspace.openLinkTextandworkspace.trigger('hover-link')). - Fixed
Ctrl + Hover(Page Preview): Because the plugin now patches the event routing layer rather than aggressively swallowing DOMmouseoverevents, Obsidian's native modifier-key detection and delays work perfectly. - Fixed Cross-File Links: Addressed a critical bug where links pointing to other files (e.g.,
[Link](other-file.md#gfm-slug)) were ignored. The new router patch correctly separates the file path from the hash before attempting resolution.
Removed
- Deleted
src/editing-mode.ts(CM6 extensions were unnecessary). - Deleted
src/reading-mode.ts(DOM Mutation Observers were unnecessary). - Deleted
src/hover-panel.ts(Custom event propagation blocking was actively breaking native behavior).
1.0.0 — 2026-07-02
Refactored
-
Monolith split into
src/modules. The 260-linemain.tswas decomposed into three
focused source files undersrc/, leavingmain.tsas a thin 13-line entry point:Module Responsibility src/gfm-slugify.tsPure utility functions — gfmSlugify()andresolveGfmSlug()src/reading-mode.tsReading view — registerMarkdownPostProcessor+MutationObserverfallbacksrc/editing-mode.tsSource mode & Live Preview — CM6 ViewPluginwith mousedown intercept -
tsconfig.jsonupdated to includesrc/**/*.tsalongside the existingmain.tsentry.
Note
No behavioral changes were introduced. The plugin logic is structurally identical to the
pre-refactor version. The reading-view path (post-processor + MutationObserver) is known to
work; the editing-mode path (CM6 ViewPlugin) was later replaced by the workspace-level
monkeypatch architecture in v1.1.0.
Changed
resolveGfmSlugnow lives insrc/gfm-slugify.tsand uses a mixed import
({ type Plugin, TFile }) becauseTFileis checked viainstanceofat runtime.registerReadingMode(plugin)replaces the inline post-processor and observer setup.createEditingModeExtension(plugin)replaces the inlineViewPlugin.fromClass(...)constructor.
Removed
- No code was removed — all logic was relocated to the appropriate
src/module.
0.1.0 — 2026-07-01
Added
- Initial plugin structure:
main.ts,manifest.json,package.json,tsconfig.json,
esbuild.config.mjs,versions.json. - GFM-style heading slug resolution via
gfmSlugify()andresolveGfmSlug(). - Reading-view link rewriting through
registerMarkdownPostProcessor. - Reading-view fallback via
MutationObserverfor dynamically-added.internal-linkelements. - Editing-mode (Source & Live Preview) link interception via a CM6
ViewPlugin.fromClass
extension that captures mousedown events.