Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package zk for openSUSE:Factory checked in at 2026-10-23 20:31:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/zk (Old) and /work/SRC/openSUSE:Factory/.zk.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "zk" Fri Oct 23 20:31:16 2026 rev:16 rq:1392056 version:0.15.7 Changes: -------- --- /work/SRC/openSUSE:Factory/zk/zk.changes 2026-07-28 18:22:43.544625471 +0200 +++ /work/SRC/openSUSE:Factory/.zk.new.2252680/zk.changes 2026-10-23 20:33:18.351164120 +0200 @@ -1,0 +2,31 @@ +Sat Oct 17 14:19:39 UTC 2026 - Andrea Manzini <[email protected]> + +- Update to version 0.15.7: + * Keep note ordering requested with --sort in fzf during + interactive filtering + * Resolve links with a leading slash against the notebook root, + falling back to the filesystem for absolute paths + * Pass a complete NoteIndex to the transaction in + NoteIndex.Commit + * Make tag filtering case-insensitive + * Use the working directory as the target of the + "edit --interactive" new note fzf binding +- Correct License tag to GPL-3.0-only AND MPL-2.0 to match + upstream LICENSE and vendored github.com/gosimple/slug +- Add BuildRequires on golang(API) >= 1.25, make, and pkgconfig +- Pin golang.org/x/[email protected] via go_modules service require + parameter and add download_files service so the upstream source + tarball stays unmodified + +------------------------------------------------------------------- +Fri Sep 4 08:08:07 UTC 2026 - Andrea Manzini <[email protected]> + +- Update Go vendored dependencies to resolve security issues + (boo#1278625, boo#1278447): + * Upgrade golang.org/x/crypto to v0.56.0 to fix security + vulnerabilities in the embedded x/crypto/ssh package: + - CVE-2026-56854: authentication bypass + - CVE-2026-56855: deadlock vulnerability + - CVE-2026-78662: deadlock vulnerability + +------------------------------------------------------------------- Old: ---- zk-0.15.6.tar.gz New: ---- zk-0.15.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ zk.spec ++++++ --- /var/tmp/diff_new_pack.ahs6ua/_old 2026-10-23 20:33:19.625217430 +0200 +++ /var/tmp/diff_new_pack.ahs6ua/_new 2026-10-23 20:33:19.627217514 +0200 @@ -17,18 +17,19 @@ Name: zk -Version: 0.15.6 +Version: 0.15.7 Release: 0 Summary: Plain text note-taking assistant for markdown -License: BSD-2-Clause -Group: System/Shells +# Legal-Review-Notice: Upstream zk is GPL-3.0-only (LICENSE); vendored github.com/gosimple/slug is MPL-2.0 (source included in vendor.tar.gz in src.rpm). +License: GPL-3.0-only AND MPL-2.0 URL: https://github.com/zk-org/zk Source0: https://github.com/zk-org/zk/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz Source1: vendor.tar.gz -# PATCH-FIX-UPSTREAM fix_test.patch -- based on commit e3c5784fb84a792491724fe4cc1f26e2c9d01b60 BuildRequires: c_compiler +BuildRequires: make +BuildRequires: pkgconfig BuildRequires: zstd -BuildRequires: golang(API) +BuildRequires: golang(API) >= 1.25 BuildRequires: pkgconfig(icu-io) BuildRequires: pkgconfig(sqlite3) Requires: fzf @@ -38,7 +39,7 @@ It is a command-line tool helping you to maintain a plain text Zettelkasten or personal wiki. %prep -%setup -qa1 +%setup -q -a1 %build %ifarch ppc64 @@ -47,13 +48,13 @@ BUILDMOD="-buildmode=pie" %endif export RPM_OPT_FLAGS="%{optflags}" -go build -v -x -mod=vendor $BUILDMOD -a -ldflags "-s -X main.Version=%{version} -X main.Build=578894f" --tags "icu json1 fts5 secure_delete" +go build -v -x -mod=vendor $BUILDMOD -a -ldflags "-s -X main.Version=%{version} -X main.Build=955cb76" --tags "icu json1 fts5 secure_delete" %install install -Dm755 zk %{buildroot}%{_bindir}/zk %check -make test +%make_build test %files %{_bindir}/%{name} ++++++ _service ++++++ --- /var/tmp/diff_new_pack.ahs6ua/_old 2026-10-23 20:33:19.720221405 +0200 +++ /var/tmp/diff_new_pack.ahs6ua/_new 2026-10-23 20:33:19.726221657 +0200 @@ -1,5 +1,7 @@ <services> + <service name="download_files" mode="manual"/> <service name="go_modules" mode="manual"> + <param name="require">golang.org/x/[email protected]</param> </service> </services> ++++++ vendor.tar.gz ++++++ ++++ 4345 lines of diff (skipped) ++++++ zk-0.15.6.tar.gz -> zk-0.15.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/.github/workflows/deploy-docs.yml new/zk-0.15.7/.github/workflows/deploy-docs.yml --- old/zk-0.15.6/.github/workflows/deploy-docs.yml 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/.github/workflows/deploy-docs.yml 2026-10-15 14:24:21.000000000 +0200 @@ -4,8 +4,7 @@ on: push: - branches: [main] - paths: [docs/**] + tags: ["v*.*.*"] concurrency: group: "pages" @@ -21,5 +20,5 @@ pages: write id-token: write steps: - - id: deployment - uses: sphinx-notes/pages@v3 + - id: deployment + uses: sphinx-notes/pages@v3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/CHANGELOG.md new/zk-0.15.7/CHANGELOG.md --- old/zk-0.15.6/CHANGELOG.md 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/CHANGELOG.md 2026-10-15 14:24:21.000000000 +0200 @@ -3,85 +3,105 @@ All notable changes to this project will be documented in this file. Pull requests can be viewed online at https://github.com/zk-org/zk/pulls -Format: `<description> (by <contributor>, <pr number>)` +Format: `<description> (by @<contributor>, #<pr number>)` ## Unreleased +... + +## 0.15.7 + +### Fixed + +- Note ordering requested with `--sort` is kept by fzf while filtering + interactively (by @paoValle, #783) +- Resolve links with a leading slash against the notebook root, falling back to + the filesystem for absolute paths (by @zmre, #745) +- `NoteIndex.Commit` now passes a complete `NoteIndex` to its transaction + instead of one missing the notebook path and note extension (by @ehsash) + +### Changed + +- Tag filtering is now case-insensitive (by @tuxikus, 770) the filesystem for + absolute paths (by @zmre, 745) +- Use the working directory as the target of the `edit --interactive` new note + fzf binding (by @skbolton, 768) + ## 0.15.6 ### Added -- Parse links to notes in frontmatter (by @tjex, 710) -- Set filters for lsp completion items from the config (by @wrvsrx, 736) +- Parse links to notes in frontmatter (by @tjex, #710) +- Set filters for lsp completion items from the config (by @wrvsrx, #736) - Set a note's modification time in frontmatter and allow for custom key naming - for both creation and modification keys (by @BennoLossin, 531) + for both creation and modification keys (by @BennoLossin, #531) ### Fixed -- Indexing made significantly more performant (by @Keluaa, 735) +- Indexing made significantly more performant (by @Keluaa, #735) - Support filtering by date and time with `"<date> <time>"` instead of - `<date>T<time>` only (by @tjex, 743) + `<date>T<time>` only (by @tjex, #743) - Exclude globs now prune matching directories from indexing, improving speed of - indexing (by @ehsash, 741) + indexing (by @ehsash, #741) ## 0.15.5 ### Added -- List, edit and filter for broken links with `--broken-links` (by - @WhyNotHugo, 708) +- List, edit and filter for broken links with `--broken-links` (by @WhyNotHugo, + #708) - Update strftime package, supporting `%g` and `%G` formats in the - `{{format-date}}` helper (by @tjex, 723) -- Option to append links to selected text, instead of replacing (by @tjex, 724) + `{{format-date}}` helper (by @tjex, #723) +- Option to append links to selected text, instead of replacing (by @tjex, #724) ### Fixed - Paths with `~` and env variables no longer error when passed to - `--notebook-dir` and `--working-dir` (by @tjex, 732) + `--notebook-dir` and `--working-dir` (by @tjex, #732) - Guard LSP against unnecessary erroring on missing textDocument/definition - capabilities (by @SAY-5, 718) + capabilities (by @SAY-5, #718) ## 0.15.4 ### Fixed -- "jump to definition" follows wrong link (by @WhyNotHugo, 703) +- "jump to definition" follows wrong link (by @WhyNotHugo, #703) ## 0.15.3 ### Added -- `zk config --list <object>` (by @andrebauer, 484) +- `zk config --list <object>` (by @andrebauer, #484) ### Fixed - Ignore commented links for LSP diagnostics. Use an AST to parse files, fixing - other similar edge cases. (by @WhyNotHugo, 638) -- Links in markdown footnotes now included in :ZkLinks (by @WhyNotHugo, 639) + other similar edge cases. (by @WhyNotHugo, #638) +- Links in markdown footnotes now included in :ZkLinks (by @WhyNotHugo, #639) - Indexing notebook now 35% and 74% faster for full and incremental indexing - respectively (by @WhyNotHugo, 642) + respectively (by @WhyNotHugo, #642) - Stop crashing lsp server when server received `textDocument/completion` - request with out of range parameters. (by @virusbb001, 667) + request with out of range parameters. (by @virusbb001, #667) - lsp: Provide completion after [[ on lines with multi-byte characters (by - @virusbb001, 671) + @virusbb001, #671) - Prevent crash in LookForward when the parameters is out of characters number. - (by @virusbb001, 673) + (by @virusbb001, #673) ## 0.15.2 ### Added - Find notes with missing backlinks using `zk list --missing-backlink` (by - @WhyNotHugo, 578) + @WhyNotHugo, #578) - LSP diagnostic for missing backlinks when other notes link to current note - without reciprocal links (by @WhyNotHugo, 577) -- Code action to add missing backlinks (by @WhyNotHugo, 581) -- LSP diagnostic for self-referential links (by @WhyNotHugo, 622) + without reciprocal links (by @WhyNotHugo, #577) +- Code action to add missing backlinks (by @WhyNotHugo, #581) +- LSP diagnostic for self-referential links (by @WhyNotHugo, #622) ### Fixed -- Release tarballs now output the program version (by @WhyNotHugo, 556) -- Config path can be set with `$ZK_CONFIG_DIR` (by @Alb-O, 575) +- Release tarballs now output the program version (by @WhyNotHugo, #556) +- Config path can be set with `$ZK_CONFIG_DIR` (by @Alb-O, #575) ## 0.15.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/CONTRIBUTING.md new/zk-0.15.7/CONTRIBUTING.md --- old/zk-0.15.6/CONTRIBUTING.md 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/CONTRIBUTING.md 2026-10-15 14:24:21.000000000 +0200 @@ -132,15 +132,18 @@ Once you're ready for a new relase, complete the following steps in order: -1. Be sure you have the latest commits on `dev`. Then insert the version heading - for the new version in `CHANGELOG.md`, commit and push. -2. Create a merge request from `dev` to `main`, titled "Release v<version>". Be - sure it passes CI and complete the merge. -3. Pull the latest changes of `main` locally. Then create a new version tag with - `git tag -a <version>`(syntax example: `v0.13.0`). Make sure you follow the - [Semantic Versioning](https://semver.org) scheme. -4. Push the tag (`git push --tags`). This will trigger the - [release action](.github/workflows/release.yml). This in turn calls the +1. Update your local `main` branch to the latest commit. +2. Check that CHANGELOG.md shows the correct incoming version number and the + changelog itself is formatted / structured as the preceding entries are. You + can format with `npx prettier CHANGELOG.md --write`. +3. Commit the changes with `git commit`. On the first line add + `Release v<version>`, leave a blank line underneath, and under that paste in + the changelog entries (removing the markdown heading symbols). See example + below. +4. Tag this commit with `git tag -a v<version>` and format the message same as + the commit message. +5. Push the commit and tag (`git push --all`). This will trigger the + [release action](.github/workflows/release.yml), which in turn calls the [build-binaries action](.github/workflows/build-binaries.yml), creates a _draft_ release on GitHub and attaches the built binaries. @@ -152,6 +155,27 @@ In both cases the description of the release can be edited after the release is created (i.e, adding or editing the changelog). +### Example release commit/tag message: + +``` +Release v0.15.5 + +Added: + +- List, edit and filter for broken links with `--broken-links` (by + @WhyNotHugo, 708) +- Update strftime package, supporting `%g` and `%G` formats in the + `{{format-date}}` helper (by @tjex, 723) +- Option to append links to selected text, instead of replacing (by @tjex, 724) + +Fixed: + +- Paths with `~` and env variables no longer error when passed to + `--notebook-dir` and `--working-dir` (by @tjex, 732) +- Guard LSP against unnecessary erroring on missing textDocument/definition + capabilities (by @SAY-5, 718) +``` + ## Benchmarks Use `make testdata` to generate test notes in `testdata`, usable for reproducing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/README.md new/zk-0.15.7/README.md --- old/zk-0.15.6/README.md 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/README.md 2026-10-15 14:24:21.000000000 +0200 @@ -17,6 +17,9 @@ video, [_Note-taking System ALL Programmers Should Consider_](https://www.youtube.com/watch?v=UzhZb7e4l4Y). +Join us on Matrix for general, support and development discussion: +[\#zk-org:pub.solar](https://matrix.to/#/#zk-org:pub.solar). + ### Highlights - [Creating notes from templates](https://zk-org.github.io/zk/notes/note-creation.html) @@ -46,12 +49,19 @@ ### What `zk` is not - A note editor. -- A tool to serve your notes on the web – for this, there are some [static site solutions](docs/tips/static-sites.md). +- A tool to serve your notes on the web – for this, there are some + [static site solutions](docs/tips/static-sites.md). ## Install [Check out the latest release](https://github.com/zk-org/zk/releases) for -pre-built binaries for macOS and Linux (`zk` was not tested on Windows). +pre-built binaries for macOS, Linux and Windows. + +> [!WARNING] The pre-built binaries are not code-signed. In the past Windows +> Defender and other security tools have flagged some versions of the Windows +> binary as malicious. Building `zk` yourself is currently the recommended +> workaround on Windows — see [Build from scratch](#build-from-scratch). See +> [#740](https://github.com/zk-org/zk/issues/740) for details. ### Homebrew @@ -67,19 +77,25 @@ ### Nix -`zk` is available in nixpkgs and has a [Home Manager](https://github.com/nix-community/home-manager) module. +`zk` is available in nixpkgs and has a +[Home Manager](https://github.com/nix-community/home-manager) module. If you want to run `zk` without permanently installing it: + ``` nix run nixpkgs#zk ``` Or, if you want to create an ephemeral shell with `zk` available: + ``` nix shell nixpkgs#zk ``` -To permanently install `zk` on NixOS at the system level, include `nixpkgs.zk` in `environment.systemPackages` in your system configuration (`/etc/nixos/configuration.nix` by default): +To permanently install `zk` on NixOS at the system level, include `nixpkgs.zk` +in `environment.systemPackages` in your system configuration +(`/etc/nixos/configuration.nix` by default): + ``` environment.systemPackages = [ # Your other packages here @@ -88,7 +104,11 @@ ``` -If you are using [Home Manager](https://github.com/nix-community/home-manager), instead of installing for all users on the system, you can permanently install and configure `zk` just for your user via the Home Manager module. Add this to your Home Manager configuration: +If you are using [Home Manager](https://github.com/nix-community/home-manager), +instead of installing for all users on the system, you can permanently install +and configure `zk` just for your user via the Home Manager module. Add this to +your Home Manager configuration: + ``` programs.zk.enable = true; @@ -133,8 +153,8 @@ make build ``` -The latest state of main can be considered the stable _pre-release_ state. -To use the absolute latest state (bugs to be expected) and to contribute: +The latest state of main can be considered the stable _pre-release_ state. To +use the absolute latest state (bugs to be expected) and to contribute: ``` git checkout dev @@ -162,12 +182,13 @@ - [Neuron](https://github.com/srid/neuron) – a great tool to publish a Zettelkasten on the web -- [Emanote](https://emanote.srid.ca/) – an improved successor to Neuron (see [Emanote configuration for zk](https://emanote.srid.ca/zk)) +- [Emanote](https://emanote.srid.ca/) – an improved successor to Neuron (see + [Emanote configuration for zk](https://emanote.srid.ca/zk)) - [Weave](https://github.com/matze/weave) – another tool to publish Zettelkasten on the web - [sirupsen's zk](https://github.com/sirupsen/zk) – a collection of scripts with a similar purpose - [zk-spaced](https://github.com/matze/zk-spaced) – spaced repetition plugin for zk -- [zk-graph-view](https://github.com/cyberSapoPerro/zk-graph-view) - -interactive graph visualization plugin for zk +- [zk-graph-view](https://github.com/cyberSapoPerro/zk-graph-view) - interactive + graph visualization plugin for zk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/VERSION.txt new/zk-0.15.7/VERSION.txt --- old/zk-0.15.6/VERSION.txt 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/VERSION.txt 2026-10-15 14:24:21.000000000 +0200 @@ -1 +1 @@ -v0.15.6 +v0.15.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/docs/notes/note-filtering.md new/zk-0.15.7/docs/notes/note-filtering.md --- old/zk-0.15.6/docs/notes/note-filtering.md 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/docs/notes/note-filtering.md 2026-10-15 14:24:21.000000000 +0200 @@ -200,6 +200,7 @@ You can filter your notes by their [tags](tags.md) using `--tags` (or `-t`). +Tag matching is case-insensitive for ASCII letters (including glob patterns). Find the notes having several tags by separating them with a comma. ```sh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/fzf/note_filter.go new/zk-0.15.7/internal/adapter/fzf/note_filter.go --- old/zk-0.15.6/internal/adapter/fzf/note_filter.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/adapter/fzf/note_filter.go 2026-10-15 14:24:21.000000000 +0200 @@ -44,6 +44,9 @@ NewNoteDir *core.Dir // Absolute path to the notebook. NotebookDir string + // Indicates whether the notes were already sorted by the caller. When they + // were, fzf keeps that order instead of ranking the matches by relevance. + Sorted bool } func NewNoteFilter(opts NoteFilterOpts, fs core.FileStorage, terminal *term.Terminal, templateLoader core.TemplateLoader) *NoteFilter { @@ -106,7 +109,7 @@ previewCmd := f.opts.PreviewCmd.OrString("cat {-1}").Unwrap() fzf, err := New(Opts{ - Options: f.opts.FzfOptions.OrString(defaultOptions), + Options: opt.NewNotEmptyString(f.fzfOptions()), PreviewCmd: opt.NewNotEmptyString(previewCmd), Padding: 2, Bindings: bindings, @@ -167,6 +170,22 @@ var defaultLineTemplate = `{{style "title" title-or-path}} {{style "understate" body}} {{style "understate" (json metadata)}}` +// fzfOptions returns the options passed to the fzf process. +// +// When the notes were already ordered by the caller, sorting is disabled so +// that fzf keeps that order instead of ranking the matches by relevance. +func (f *NoteFilter) fzfOptions() string { + options := f.opts.FzfOptions.OrString(defaultOptions).String() + if f.opts.Sorted { + options += " " + noSortOption + } + return options +} + +// noSortOption tells fzf not to sort its input, preserving the order in which +// the notes were sent to it. +const noSortOption = "--no-sort" + // defaultOptions are the default fzf options used when filtering notes. var defaultOptions = strings.Join([]string{ "--tiebreak begin", // Prefer matches located at the beginning of the line diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/fzf/note_filter_test.go new/zk-0.15.7/internal/adapter/fzf/note_filter_test.go --- old/zk-0.15.6/internal/adapter/fzf/note_filter_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/zk-0.15.7/internal/adapter/fzf/note_filter_test.go 2026-10-15 14:24:21.000000000 +0200 @@ -0,0 +1,29 @@ +package fzf + +import ( + "testing" + + "github.com/zk-org/zk/internal/util/opt" + "github.com/zk-org/zk/internal/util/test/assert" +) + +func TestNoteFilterFzfOptions(t *testing.T) { + test := func(name string, opts NoteFilterOpts, expected string) { + filter := NoteFilter{opts: opts} + assert.Equal(t, filter.fzfOptions(), expected) + } + + test("keep the default options when the notes are not sorted", + NoteFilterOpts{}, defaultOptions) + + test("disable fzf sorting when the notes are sorted", + NoteFilterOpts{Sorted: true}, + defaultOptions+" --no-sort") + + test("keep the configured fzf options when the notes are sorted", + NoteFilterOpts{ + FzfOptions: opt.NewNotEmptyString("--height 50%"), + Sorted: true, + }, + "--height 50% --no-sort") +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/lsp/server.go new/zk-0.15.7/internal/adapter/lsp/server.go --- old/zk-0.15.6/internal/adapter/lsp/server.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/adapter/lsp/server.go 2026-10-15 14:24:21.000000000 +0200 @@ -547,15 +547,37 @@ // noteForHref returns the Note object for the note targeted by the given HREF // relative to relativeToDir. +// +// If there's a leading slash, resolve against the notebook root first to match +// most Markdown renderers. Fallback to the filesystem root. func (s *Server) noteForHref(href string, relativeToDir string, notebook *core.Notebook) (*core.MinimalNote, error) { if strutil.IsURL(href) { return nil, nil } + if strings.HasPrefix(href, "/") || filepath.IsAbs(href) { + note, err := s.noteForPath(filepath.Join(notebook.Path, href), href, notebook) + if note != nil || err != nil { + return note, err + } + if !filepath.IsAbs(href) { + // A rooted but non-absolute path (e.g. /foo on Windows) can't be + // made relative to the notebook, so don't attempt the fallback. + return nil, nil + } + return s.noteForPath(filepath.Clean(href), href, notebook) + } + path := href if relativeToDir != "" { path = filepath.Clean(filepath.Join(relativeToDir, path)) } + return s.noteForPath(path, href, notebook) +} + +// noteForPath returns the Note object for the note at the given absolute +// filesystem path, or nil if it doesn't target a note in the notebook. +func (s *Server) noteForPath(path string, href string, notebook *core.Notebook) (*core.MinimalNote, error) { path, err := filepath.Rel(notebook.Path, path) if err != nil { return nil, fmt.Errorf("failed to resolve href: %s: %w", href, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/lsp/server_test.go new/zk-0.15.7/internal/adapter/lsp/server_test.go --- old/zk-0.15.6/internal/adapter/lsp/server_test.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/adapter/lsp/server_test.go 2026-10-15 14:24:21.000000000 +0200 @@ -224,3 +224,115 @@ } assert.Equal(t, definitionLinkSupport(caps), true) } + +func TestServer_noteForHref(t *testing.T) { + fixture := getNotebookFixture("links") + + fs, err := fs.NewFileStorage(fixture.Path, &util.NullLogger) + if err != nil { + t.Fatal(err) + } + fixture.FS = fs + + // Setup DB + db, err := sqlite.OpenInMemory() + if err != nil { + t.Fatal(err) + } + defer db.Close() + + config := core.NewDefaultConfig() + index := sqlite.NewNoteIndex(fixture.Path, db, &util.NullLogger, config.Note.Extension) + + // Initialize markdown parser directly + parser := markdown.NewParser(markdown.ParserOpts{ + HashtagEnabled: true, + }, &util.NullLogger) + + notebook := core.NewNotebook(fixture.Path, config, core.NotebookPorts{ + NoteIndex: index, + NoteContentParser: parser, + FS: fs, + TemplateLoaderFactory: func(lang string) (core.TemplateLoader, error) { + return &core.NullTemplateLoader, nil + }, + Logger: &util.NullLogger, + }) + if _, err := notebook.Index(core.NoteIndexOpts{ + Force: true, + Verbose: false, + }); err != nil { + t.Fatal(err) + } + + server := &Server{ + documents: newDocumentStore(fs, &util.NullLogger), + logger: &util.NullLogger, + } + + tests := []struct { + name string + href string + relativeToDir string + // Expected note path relative to the notebook root, or "" when no + // note should be found. + wantPath string + }{{ + name: "relative to the note's directory", + href: "note-a", + relativeToDir: filepath.Join(fixture.Path, "sub"), + wantPath: "sub/note-a.md", + }, { + name: "relative with parent traversal", + href: "../top", + relativeToDir: filepath.Join(fixture.Path, "sub"), + wantPath: "top.md", + }, { + name: "notebook-root path from a nested directory", + href: "/sub/note-a", + relativeToDir: filepath.Join(fixture.Path, "sub", "nested"), + wantPath: "sub/note-a.md", + }, { + name: "notebook-root path with extension", + href: "/sub/nested/note-b.md", + relativeToDir: fixture.Path, + wantPath: "sub/nested/note-b.md", + }, { + name: "notebook-root path to a top-level note", + href: "/top", + relativeToDir: filepath.Join(fixture.Path, "sub", "nested"), + wantPath: "top.md", + }, { + name: "filesystem absolute path fallback", + href: filepath.Join(fixture.Path, "sub", "note-a.md"), + relativeToDir: filepath.Join(fixture.Path, "sub"), + wantPath: "sub/note-a.md", + }, { + name: "dead notebook-root link", + href: "/does-not-exist", + relativeToDir: filepath.Join(fixture.Path, "sub"), + wantPath: "", + }, { + name: "URL is ignored", + href: "https://example.com/foo", + relativeToDir: filepath.Join(fixture.Path, "sub"), + wantPath: "", + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + note, err := server.noteForHref(tt.href, tt.relativeToDir, notebook) + assert.Nil(t, err) + if tt.wantPath == "" { + if note != nil { + t.Errorf("expected no note for %s, got %s", tt.href, note.Path) + } + } else { + if note == nil { + t.Fatalf("expected note %s for %s, got nil", tt.wantPath, tt.href) + } + assert.Equal(t, note.Path, tt.wantPath) + } + }) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/lsp/testdata/links/.zk/config.toml new/zk-0.15.7/internal/adapter/lsp/testdata/links/.zk/config.toml --- old/zk-0.15.6/internal/adapter/lsp/testdata/links/.zk/config.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/zk-0.15.7/internal/adapter/lsp/testdata/links/.zk/config.toml 2026-10-15 14:24:21.000000000 +0200 @@ -0,0 +1,11 @@ +[note] +template = "default.md" + +[format.markdown] + +link-format = "markdown" + +[lsp] + +[lsp.diagnostics] +dead-link = "error" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/lsp/testdata/links/sub/nested/note-b.md new/zk-0.15.7/internal/adapter/lsp/testdata/links/sub/nested/note-b.md --- old/zk-0.15.6/internal/adapter/lsp/testdata/links/sub/nested/note-b.md 1970-01-01 01:00:00.000000000 +0100 +++ new/zk-0.15.7/internal/adapter/lsp/testdata/links/sub/nested/note-b.md 2026-10-15 14:24:21.000000000 +0200 @@ -0,0 +1,3 @@ +# Note B + +A note in a nested subdirectory. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/lsp/testdata/links/sub/note-a.md new/zk-0.15.7/internal/adapter/lsp/testdata/links/sub/note-a.md --- old/zk-0.15.6/internal/adapter/lsp/testdata/links/sub/note-a.md 1970-01-01 01:00:00.000000000 +0100 +++ new/zk-0.15.7/internal/adapter/lsp/testdata/links/sub/note-a.md 2026-10-15 14:24:21.000000000 +0200 @@ -0,0 +1,3 @@ +# Note A + +A note in a subdirectory. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/lsp/testdata/links/top.md new/zk-0.15.7/internal/adapter/lsp/testdata/links/top.md --- old/zk-0.15.6/internal/adapter/lsp/testdata/links/top.md 1970-01-01 01:00:00.000000000 +0100 +++ new/zk-0.15.7/internal/adapter/lsp/testdata/links/top.md 2026-10-15 14:24:21.000000000 +0200 @@ -0,0 +1,3 @@ +# Top + +A note at the notebook root. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/sqlite/note_dao.go new/zk-0.15.7/internal/adapter/sqlite/note_dao.go --- old/zk-0.15.6/internal/adapter/sqlite/note_dao.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/adapter/sqlite/note_dao.go 2026-10-15 14:24:21.000000000 +0200 @@ -32,7 +32,6 @@ findIDsByFilenameLikeStmt *LazyStmt findIDsByPathLikeStmt *LazyStmt findIDsByPathPrefixStmt *LazyStmt - findByIDStmt *LazyStmt } // NewNoteDAO creates a new instance of a DAO working on the given database @@ -95,13 +94,6 @@ OR path LIKE ? ESCAPE '\' ORDER BY LENGTH(path) ASC `), - - // Find a note from its ID. - findByIDStmt: tx.PrepareLazy(` - SELECT id, path, title, lead, body, raw_content, word_count, created, modified, metadata, checksum, tags, lead AS snippet - FROM notes_with_metadata - WHERE id = ? - `), } } @@ -599,7 +591,7 @@ if len(tag) == 0 { continue } - globs = append(globs, "t.name GLOB ?") + globs = append(globs, "LOWER(t.name) GLOB LOWER(?)") args = append(args, tag) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/sqlite/note_dao_test.go new/zk-0.15.7/internal/adapter/sqlite/note_dao_test.go --- old/zk-0.15.6/internal/adapter/sqlite/note_dao_test.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/adapter/sqlite/note_dao_test.go 2026-10-15 14:24:21.000000000 +0200 @@ -403,19 +403,38 @@ } test([]string{"fiction"}, []string{"log/2021-01-03.md"}) + test([]string{"FICTION"}, []string{"log/2021-01-03.md"}) + test([]string{"FiCtIoN"}, []string{"log/2021-01-03.md"}) test([]string{" adventure "}, []string{"ref/test/b.md", "log/2021-01-03.md"}) test([]string{"fiction", "adventure"}, []string{"log/2021-01-03.md"}) test([]string{"fiction|fantasy"}, []string{"f39c8.md", "log/2021-01-03.md"}) + test([]string{"FICTION|FANTASY"}, []string{"f39c8.md", "log/2021-01-03.md"}) + test([]string{"FIC*"}, []string{"log/2021-01-03.md"}) test([]string{"fiction | fantasy"}, []string{"f39c8.md", "log/2021-01-03.md"}) test([]string{"fiction OR fantasy"}, []string{"f39c8.md", "log/2021-01-03.md"}) test([]string{"fiction | adventure | fantasy"}, []string{"ref/test/b.md", "f39c8.md", "log/2021-01-03.md"}) test([]string{"fiction | history", "adventure"}, []string{"ref/test/b.md", "log/2021-01-03.md"}) test([]string{"fiction", "unknown"}, []string{}) test([]string{"-fiction"}, []string{"ref/test/ref.md", "ref/test/b.md", "f39c8.md", "ref/test/a.md", "log/2021-02-04.md", "index.md", "log/2021-01-04.md"}) + test([]string{"-FICTION"}, []string{"ref/test/ref.md", "ref/test/b.md", "f39c8.md", "ref/test/a.md", "log/2021-02-04.md", "index.md", "log/2021-01-04.md"}) test([]string{"NOT fiction"}, []string{"ref/test/ref.md", "ref/test/b.md", "f39c8.md", "ref/test/a.md", "log/2021-02-04.md", "index.md", "log/2021-01-04.md"}) test([]string{"NOTfiction"}, []string{"ref/test/ref.md", "ref/test/b.md", "f39c8.md", "ref/test/a.md", "log/2021-02-04.md", "index.md", "log/2021-01-04.md"}) } +func TestNoteDAOFindTagMixedCaseStored(t *testing.T) { + testNoteDAO(t, func(tx Transaction, dao *NoteDAO) { + _, err := tx.Exec("UPDATE collections SET name = 'FiCtIoN' WHERE kind = 'tag' AND name = 'fiction'") + assert.Nil(t, err) + + for _, tag := range []string{"fiction", "FICTION", "fic*"} { + matches, err := dao.Find(core.NoteFindOpts{Tags: []string{tag}}) + assert.Nil(t, err) + assert.Equal(t, len(matches), 1) + assert.Equal(t, matches[0].Path, "log/2021-01-03.md") + } + }) +} + func TestNoteDAOFindMatch(t *testing.T) { testNoteDAOFind(t, core.NoteFindOpts{ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/sqlite/note_index.go new/zk-0.15.7/internal/adapter/sqlite/note_index.go --- old/zk-0.15.6/internal/adapter/sqlite/note_index.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/adapter/sqlite/note_index.go 2026-10-15 14:24:21.000000000 +0200 @@ -345,10 +345,15 @@ // Commit implements core.NoteIndex. func (ni *NoteIndex) Commit(transaction func(idx core.NoteIndex) error) error { return ni.commit(func(dao *dao) error { + // Carry every field over so the index handed to the transaction + // behaves like its receiver; a partial copy leaves zero-value fields + // that silently diverge from the outer index. return transaction(&NoteIndex{ - db: ni.db, - dao: dao, - logger: ni.logger, + notebookPath: ni.notebookPath, + db: ni.db, + dao: dao, + logger: ni.logger, + extension: ni.extension, }) }) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/adapter/sqlite/note_index_commit_test.go new/zk-0.15.7/internal/adapter/sqlite/note_index_commit_test.go --- old/zk-0.15.6/internal/adapter/sqlite/note_index_commit_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/zk-0.15.7/internal/adapter/sqlite/note_index_commit_test.go 2026-10-15 14:24:21.000000000 +0200 @@ -0,0 +1,30 @@ +package sqlite + +import ( + "testing" + + "github.com/zk-org/zk/internal/core" + "github.com/zk-org/zk/internal/util" + "github.com/zk-org/zk/internal/util/test/assert" +) + +// Commit hands the transaction a NoteIndex built from the receiver. That index +// must carry every field of the receiver: the whole indexing pass runs inside +// this transaction (see Notebook.IndexWithCallback), and linkMatchesPath reads +// notebookPath to resolve a link relative to its source note. Dropping the +// field left the transaction resolving links against an empty notebook path. +func TestNoteIndexCommitPreservesFields(t *testing.T) { + db := testDB(t) + defer db.Close() + + index := NewNoteIndex("/notebook", db, &util.NullLogger, "markdown") + + err := index.Commit(func(idx core.NoteIndex) error { + inner, ok := idx.(*NoteIndex) + assert.True(t, ok) + assert.Equal(t, inner.notebookPath, "/notebook") + assert.Equal(t, inner.extension, "markdown") + return nil + }) + assert.Nil(t, err) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/cli/cmd/edit.go new/zk-0.15.7/internal/cli/cmd/edit.go --- old/zk-0.15.6/internal/cli/cmd/edit.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/cli/cmd/edit.go 2026-10-15 14:24:21.000000000 +0200 @@ -37,6 +37,7 @@ AlwaysFilter: true, NewNoteDir: cmd.newNoteDir(notebook), NotebookDir: notebook.Path, + Sorted: len(findOpts.Sorters) > 0, }) notes, err = filter.Apply(notes) @@ -81,7 +82,10 @@ func (cmd *Edit) newNoteDir(notebook *core.Notebook) *core.Dir { switch len(cmd.Path) { case 0: - dir := notebook.RootDir() + dir, err := notebook.DirAt(".") + if err != nil { + dir = notebook.RootDir() + } return &dir case 1: dir, err := notebook.DirAt(cmd.Path[0]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/cli/cmd/edit_test.go new/zk-0.15.7/internal/cli/cmd/edit_test.go --- old/zk-0.15.6/internal/cli/cmd/edit_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/zk-0.15.7/internal/cli/cmd/edit_test.go 2026-10-15 14:24:21.000000000 +0200 @@ -0,0 +1,100 @@ +package cmd + +import ( + "os" + "path/filepath" + "testing" + + "github.com/zk-org/zk/internal/adapter/fs" + "github.com/zk-org/zk/internal/adapter/handlebars" + "github.com/zk-org/zk/internal/adapter/markdown" + "github.com/zk-org/zk/internal/core" + "github.com/zk-org/zk/internal/util" + "github.com/zk-org/zk/internal/util/opt" + "github.com/zk-org/zk/internal/util/test/assert" +) + +// Simulates the "create a note" fzf binding of `zk edit --interactive`, which +// creates a note in the directory resolved for the user's location. +func TestEditInteractiveNewNote(t *testing.T) { + notebookDir := t.TempDir() + + for _, dir := range []string{"journal", ".zk/templates"} { + err := os.MkdirAll(filepath.Join(notebookDir, dir), 0o700) + assert.Equal(t, err, nil) + } + err := os.WriteFile( + filepath.Join(notebookDir, ".zk/templates/default.md"), + []byte("Default: {{title}}\n"), + 0o600, + ) + assert.Equal(t, err, nil) + err = os.WriteFile( + filepath.Join(notebookDir, ".zk/templates/journal.md"), + []byte("Journal: {{title}}\n"), + 0o600, + ) + assert.Equal(t, err, nil) + + tests := []struct { + name string + workingDir string + expectedPath string + expectedBody string + }{ + { + name: "a note created from a group dir is a note of that group", + workingDir: notebookDir + "/journal", + expectedPath: "journal/test.md", + expectedBody: "Journal: Test\n", + }, + { + name: "a note created from the notebook root uses the root config", + workingDir: notebookDir, + expectedPath: "test.md", + expectedBody: "Default: Test\n", + }, + { + name: "a note created from outside the notebook uses the root config", + workingDir: "/somewhere/else", + expectedPath: "test.md", + expectedBody: "Default: Test\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs, err := fs.NewFileStorage(tt.workingDir, &util.NullLogger) + assert.Equal(t, err, nil) + config := core.NewDefaultConfig() + config.Note.BodyTemplatePath = opt.NewString("default.md") + journalNote := config.Note + journalNote.BodyTemplatePath = opt.NewString("journal.md") + config.Groups["journal"] = core.GroupConfig{Paths: []string{"journal"}, Note: journalNote} + notebook := core.NewNotebook(notebookDir, config, core.NotebookPorts{ + FS: fs, + NoteContentParser: markdown.NewParser(markdown.ParserOpts{}, &util.NullLogger), + TemplateLoaderFactory: func(lang string) (core.TemplateLoader, error) { + return handlebars.NewLoader(handlebars.LoaderOpts{ + LookupPaths: []string{filepath.Join(notebookDir, ".zk/templates")}, + Styler: core.NullStyler, + }), nil + }, + IDGeneratorFactory: func(opts core.IDOptions) func() string { + return func() string { return "test" } + }, + OSEnv: func() map[string]string { return map[string]string{} }, + }) + + dir := (&Edit{}).newNoteDir(notebook) + note, err := notebook.NewNote(core.NewNoteOpts{ + Directory: opt.NewNotEmptyString(dir.Path), + Title: opt.NewNotEmptyString("Test"), + DryRun: true, + }) + assert.Equal(t, err, nil) + assert.Equal(t, note.Path, tt.expectedPath) + assert.Equal(t, note.RawContent, tt.expectedBody) + }) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/cli/cmd/graph.go new/zk-0.15.7/internal/cli/cmd/graph.go --- old/zk-0.15.6/internal/cli/cmd/graph.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/cli/cmd/graph.go 2026-10-15 14:24:21.000000000 +0200 @@ -51,6 +51,7 @@ Interactive: cmd.Interactive, AlwaysFilter: false, NotebookDir: notebook.Path, + Sorted: len(findOpts.Sorters) > 0, }) notes, err = filter.Apply(notes) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/cli/cmd/list.go new/zk-0.15.7/internal/cli/cmd/list.go --- old/zk-0.15.6/internal/cli/cmd/list.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/cli/cmd/list.go 2026-10-15 14:24:21.000000000 +0200 @@ -93,6 +93,7 @@ Interactive: cmd.Interactive, AlwaysFilter: false, NotebookDir: notebook.Path, + Sorted: len(findOpts.Sorters) > 0, }) notes, err = filter.Apply(notes) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zk-0.15.6/internal/util/date/date.go new/zk-0.15.7/internal/util/date/date.go --- old/zk-0.15.6/internal/util/date/date.go 2026-07-25 21:30:37.000000000 +0200 +++ new/zk-0.15.7/internal/util/date/date.go 2026-10-15 14:24:21.000000000 +0200 @@ -6,35 +6,6 @@ naturaldate "github.com/tj/go-naturaldate" ) -// Provider returns a date instance. -type Provider interface { - Date() time.Time -} - -// Now is a date provider returning the current date. -type Now struct{} - -func (n *Now) Date() time.Time { - return time.Now() -} - -// Frozen is a date provider returning always the same date. -type Frozen struct { - date time.Time -} - -func NewFrozenNow() Frozen { - return Frozen{time.Now()} -} - -func NewFrozen(date time.Time) Frozen { - return Frozen{date} -} - -func (n *Frozen) Date() time.Time { - return n.date -} - // TimeFromNatural parses a human date into a time.Time. func TimeFromNatural(date string) (time.Time, error) { if date == "" {
