Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7e2c864
Hot-reload .emanoteignore mid-session (#739)
srid May 17, 2026
c669baa
refactor(hickey): wire isLayerRootIgnoreFile into the reload guard
srid May 17, 2026
465962b
refactor(lowy): cross-reference Patch.IgnoreFile and Dynamic.handleIg…
srid May 17, 2026
f85d647
refactor(lowy): use makeLenses for IgnoreState and ModifiedEvent
srid May 17, 2026
cac5afb
refactor(hickey): extract OverlayOutcome / classifyOverlays helper
srid May 17, 2026
c408e88
refactor(hickey): collapse IgnoreState into a single TVar holding a s…
srid May 17, 2026
936d76b
refactor(hickey): bundle session-stable params into HandlerCtx
srid May 17, 2026
c231ea5
fix(hickey): extend hot-reload eviction to static files
srid May 17, 2026
ea51599
fix(police): docs-internal-wikilinks — link cascade/template caveat t…
srid May 17, 2026
9365646
fix(police): fact-check — preserve overlay history on second eviction
srid May 17, 2026
1d1f704
refactor(police): elegance — use HasExt.fileType class for lmlRouteFi…
srid May 17, 2026
ebc477a
refactor(police): elegance — trim narrating Haddock on trivial helpers
srid May 17, 2026
5d385d8
refactor(police): elegance — replace duplicated Patch.IgnoreFile pros…
srid May 17, 2026
e256b55
refactor(police): elegance — short-circuit classifyOverlays when patt…
srid May 17, 2026
92ac366
refactor(police): elegance — restrict eviction walks to layers whose …
srid May 17, 2026
b756c61
refactor(police): elegance — extract writeStaged test helper
srid May 17, 2026
159f3c8
refactor(police): elegance — extract pollUrl helper for HTTP polling …
srid May 17, 2026
0006083
fix: don't let universal **/.*/** swallow root-level .emanoteignore
srid May 17, 2026
729b76a
docs: note the root-dotfile exposure side-effect in the changelog
srid May 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/guide/emanoteignore.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ Patterns in a `.emanoteignore` only suppress files inside the layer they live in

Universal ignores (dotfile directories, editor backup files, the reserved `-/` directory, the `.emanoteignore` file itself) are still enforced globally — those don't need to be repeated per layer.

## Hot reload

Editing `.emanoteignore` mid-session takes effect immediately, without restarting `emanote run` (closes [#739](https://github.com/srid/emanote/issues/739)). Adding a pattern evicts matching notes and static files from the model and the sidebar; removing a pattern brings the previously-hidden notes and static files back. Multi-layer notebooks update on a per-layer basis: editing `layer-A/.emanoteignore` only re-evaluates files inside layer A.

YAML data files (e.g. `index.yaml` driving the metadata cascade, see [[yaml-config]]) and Heist `.tpl` templates (see [[html-template]]) carry no per-layer source metadata in the model today; adding a pattern that matches one mid-session still leaves the resolved cascade / template in memory until restart. Edits that *remove* such a pattern are unaffected because those files were never filtered out in the first place.

## Common patterns

| Use case | Pattern |
Expand Down
1 change: 1 addition & 0 deletions emanote/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- **Inline-SVG diagrams (`d2`, `cetz`) + Lua-filter error protocol** ([#625](https://github.com/srid/emanote/issues/625)) — bundled `pandoc-ext/diagram` opt-in, offline `@preview/cetz` cache, injected `emanote.error_block` helper, `--allow-broken-lua-filters` flag. See [[diagrams]] and [[writing-filters]].
- **Lua filter hot-reload, bundled filters, and render-time filters** (closes the MVP→final half of [#263](https://github.com/srid/emanote/issues/263), tracked in [#721](https://github.com/srid/emanote/issues/721)): editing a `.lua` file referenced from any note's Markdown `pandoc.filters.parse` / `pandoc.filters.render.html` frontmatter or Org `#+PANDOC_FILTERS_PARSE` / `#+PANDOC_FILTERS_RENDER_HTML` keywords now refreshes every dependent note in place — no `touch foo.md` workaround required. Parse-time filters run with `FORMAT == "markdown"` and with IO-capable Lua/Pandoc APIs disabled; HTML render-time filters run with `FORMAT == "html"` and receive the note's effective metadata in `doc.meta`. The reverse-dependency index keys edges by the filter path *as written* (typically layer-relative), so a filter referenced before it exists on disk also gets an edge: creating the file later triggers re-parse. `.lua` is also now claimed as its own file type so edits route through the hot-reload path while filter sources remain wikilinkable as source files. Emanote now bundles the maintained [`pandoc-ext/list-table`](https://github.com/pandoc-ext/list-table) filter plus an Emanote-specific `wordcount.lua` demo filter, so notes can declare `pandoc.filters.parse: [lua-filters/list-table.lua]` without copying the filter into the notebook. The docs include a render-time custom `slides.lua` powering [`/slides`](https://emanote.srid.ca/slides), itself a Markdown deck *about* Lua filters. Built on `unionmount`'s new `unionMountStreaming` so the patch handler reads the running model directly without maintaining a parallel mirror.
- **`.emanoteignore`** (closes [#228](https://github.com/srid/emanote/issues/228)): each notebook layer may now ship a top-level `.emanoteignore` listing `FilePattern` entries (one per line; blanks and `#`-comment lines skipped) to exclude files from the model. Patterns are scoped to the layer they live in — a pattern in layer A's file does not affect layer B — and are merged with Emanote's universal ignores (`**/.*/**`, `**/*~`, `-/**`, and `**/.emanoteignore` itself). Built on per-source ignore support added to `unionmount`. **Behavior change:** `flake.nix` and `flake.lock` are no longer ignored by default — users who run Emanote from inside a Nix flake notebook should add those entries to their own `.emanoteignore`.
- **`.emanoteignore` hot-reload** (closes [#739](https://github.com/srid/emanote/issues/739)): edits to a layer's `.emanoteignore` during `emanote run` now take effect without a restart — added patterns evict matching notes (and static files) from the model and the sidebar, removed patterns bring previously-hidden ones back. See [[emanoteignore]]. **Behavior change:** root-level standalone dotfiles (e.g. `.gitignore`, `.envrc`, `.editorconfig`) that the previous universal pattern incidentally swallowed are now exposed as static assets unless listed in the user's own `.emanoteignore`. The pattern change is the one that lets the hot-reload pipeline see `.emanoteignore` itself; dotfile *directories* (`.git/`, `.vscode/`) are still ignored.
- **MCP server** scaffolding: new `emanote run --mcp-port PORT` flag runs an in-process Model Context Protocol HTTP endpoint beside the live server. Phase 1 ships only the lifecycle handshake and empty resource/tool inventories; richer surfaces follow in later phases ([#645](https://github.com/srid/emanote/issues/645))
- Default template chrome can now be localized through `page.lang` and `template.i18n`. English remains the fallback language, and French and Chinese strings are included for the built-in navigation, search, copy buttons, labels, and error chrome (closes [#486](https://github.com/srid/emanote/issues/486), [#722](https://github.com/srid/emanote/pull/722)).
- Callouts: support **nested** and **foldable** Obsidian-style callouts (`> [!type]+` / `[!type]-`), rendering as `<details>`/`<summary>` ([#465](https://github.com/srid/emanote/issues/465), [#652](https://github.com/srid/emanote/pull/652))
Expand Down
8 changes: 8 additions & 0 deletions emanote/src/Emanote/Model/StaticFile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Data.IxSet.Typed (Indexable (..), IxSet, ixFun, ixList)
import Data.Text qualified as T
import Data.Time (UTCTime)
import Emanote.Route qualified as R
import Emanote.Source.Loc (Loc)
import Optics.TH (makeLenses)
import Relude
import Skylighting qualified
Expand All @@ -21,6 +22,13 @@ data StaticFile = StaticFile
-- ^ Indicates that this file was updated no latter than the given time.
, _staticFileInfo :: Maybe StaticFileInfo
-- ^ This file might have its content read
, _staticFileSource :: Maybe (Loc, FilePath)
-- ^ Layer + layer-relative path of the top overlay for this file.
-- 'Nothing' only when the static file is synthesised without a
-- backing source (currently never — populated unconditionally by
-- 'Emanote.Source.Patch.insertStaticFile'). Used by the
-- @.emanoteignore@ hot-reload walk in 'Emanote.Source.Dynamic' to
-- decide which static files to evict when a pattern starts matching.
}
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (Aeson.ToJSON)
Expand Down
6 changes: 3 additions & 3 deletions emanote/src/Emanote/Model/Type.hs
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,11 @@ modelLookupStaticFile fp m = do
r :: R.R 'AnyExt <- R.mkRouteFromFilePath fp
Ix.getOne $ Ix.getEQ r $ m ^. modelStaticFiles

modelInsertStaticFile :: UTCTime -> R.R 'AnyExt -> FilePath -> Maybe StaticFileInfo -> ModelT f -> ModelT f
modelInsertStaticFile t r fp mInfo =
modelInsertStaticFile :: UTCTime -> R.R 'AnyExt -> FilePath -> Maybe StaticFileInfo -> Maybe (Loc, FilePath) -> ModelT f -> ModelT f
modelInsertStaticFile t r fp mInfo mSrc =
modelStaticFiles %~ Ix.updateIx r staticFile
where
staticFile = StaticFile r fp t mInfo
staticFile = StaticFile r fp t mInfo mSrc

modelDeleteStaticFile :: R.R 'AnyExt -> ModelT f -> ModelT f
modelDeleteStaticFile r =
Expand Down
6 changes: 6 additions & 0 deletions emanote/src/Emanote/Route/Ext.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ data FileType a where
-- hot-reload path before the file is also indexed as linkable source.
-- See "Emanote.Model.SourceDependencies".
LuaFilter :: FileType SourceExt
-- | A @.emanoteignore@ file. Tracked as its own type so unionmount
-- delivers edits to it through the same fsnotify pipeline as other
-- source files; the handler then re-parses the patterns and walks
-- the model to evict or re-include affected notes without restart.
-- See "Emanote.Source.Ignore" and "Emanote.Source.Dynamic".
IgnoreFile :: FileType SourceExt
-- | `AnyExt` has no *known* (at compile time) extension. It is used as a
-- "catch all" type to capture files using an arbitrary.
AnyExt :: FileType SourceExt
Expand Down
Loading
Loading