Skip to content

feat(lazy-parity): add defaults.lazy and defaults.version#29

Merged
zuqini merged 9 commits into
mainfrom
feat/lazy-nvim-parity-defaults-and-validation
May 27, 2026
Merged

feat(lazy-parity): add defaults.lazy and defaults.version#29
zuqini merged 9 commits into
mainfrom
feat/lazy-nvim-parity-defaults-and-validation

Conversation

@zuqini

@zuqini zuqini commented May 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • defaults.lazy = true makes every spec lazy unless it sets lazy = false (lazy.nvim parity).
  • defaults.version = '<branch|tag|range>' applies a default version when a spec has no version/sem_version/branch/tag/commit. Per-spec version = false opts out; defaults.version = false and = true are silent no-ops.
  • Doc additions: README, helpdoc (*zpack-migration-enabled-cond*), and docs/tips.md cover the new defaults and the enabled vs cond distinction.
  • Refactor: lifted default_lazy() and has_lazy_triggers() helpers in lazy.lua; is_lazy short-circuits on the global floor before resolving function-form triggers.
  • Validator accepts defaults.lazy (boolean) and defaults.version (string/table/boolean).

Test plan

  • nvim -u NONE -l tests/busted.lua — full suite green
  • luacheck lua/ tests/
  • lua-language-server --check
  • New tests cover precedence (per-spec wins over defaults), the version = false escape hatch, dep-inheritance under defaults.lazy, and enabled = false still prunes under defaults.lazy = true

zuqini added 9 commits May 25, 2026 20:14
Three lazy.nvim parity polish items so author-published specs and
lazy.nvim configs port more cleanly:

- defaults.lazy / defaults.version: setup() now honors a global lazy
  flag and a global version fallback, applied only when a spec doesn't
  set its own (version=false still opts out). Threaded through
  lazy.is_lazy + lazy.has_lazy_parent so dep-only children of a
  defaults.lazy parent are also lazy.
- validate.lua: warns (does not error) on rocks/virtual/submodules —
  fields zpack silently ignored before. Behavior unchanged; users now
  see the gap.
- docs/tips.md: short migration notes for the enabled/cond split
  (zpack separates "skip install" vs "skip load") and the
  unsupported-field list.
A user who ignored the validator's "expected string|table" warning would leak
boolean false through normalize_version into vim.pack.add. Treat it as nil
instead, mirroring the per-spec version=false escape hatch.

Also document defaults.lazy/defaults.version in doc/zpack.txt (README and
the LuaCATS class already had them) and add regression guards for the
version priority chain (branch/sem_version vs defaults.version) and the
defaults.lazy + explicit-trigger interaction.

Trim verbose doc-comments on the same change for the no-comments-unless-
non-obvious rule.
The runtime treats defaults.version=false as the no-default opt-out
(utils.normalize_version, pinned by version_test.lua "defaults.version=false
is treated as no default") but validate_config typed the field as
{string, table} only, so users writing the documented opt-out got a
spurious "expected string|table, got boolean" notify at every setup().

Widens the validator to {string, table, boolean}, mirroring
SPEC_FIELD_TYPES.version which already accepts the same shape for
per-spec values.
is_lazy and has_lazy_parent both evaluated event/cmd/ft/keys triggers
and the defaults.lazy floor, with the floor woven in slightly differently
at each site (terminal fallback vs. OR'd into the trigger check). The
trigger-OR-floor decision now lives in one is_lazy_by_triggers_or_floor
local; both callers route through it, so a future refinement to lazy
resolution lands in one place.

Behavior-preserving (497/0 busted, validated under defaults.lazy=true and
false on triggerless specs, dep-of-lazy-parent, explicit spec.lazy
opt-out, and triggered specs).
is_lazy_by_triggers_or_floor now checks defaults.lazy before resolving
trigger fields. When the floor implies lazy, the four try_resolve_field
calls are skipped, eliminating up to four duplicate "Failed to resolve"
notifies per setup() for throwing function-form resolvers in the
defaults.lazy=true case — the same redundant-notify concern the existing
"try_resolve_field re-invokes the same function-form spec field across
the setup pipeline (no caching)" decision flagged. Same answer either
order, since the helper is a disjunction.

defaults.version's LuaCATS annotation gains |false to match the validator
and utils.normalize_version (types.lua already does this for the per-spec
field); silences an LSP false-positive on the documented
`defaults = { version = false }` opt-out.

Also collapse default_lazy() to the bare-`and` idiom used elsewhere in
the PR (utils.normalize_version line 347), and drop one inline comment
in normalize_version that the doc-comment above it already covers.

497/0 busted, 0/0 luacheck.
797bbff added a per-spec warning loop in validate_spec that flagged
rocks/submodules/virtual as "unsupported lazy.nvim field, ignored by
zpack". Revert: the project's framing is "lazy.nvim spec drop-in
compatibility" for the spec shape, not "policer of authoring style".
Warning at setup() time on every unknown field shifts zpack from "thin
layer over vim.pack" to runtime gatekeeper, which is scope creep.

The README and docs/tips.md still document the gaps once, which is the
right surface for migrating users. A truly load-bearing field (e.g.
rocks enabling LuaRocks deps) surfaces as a runtime require-failure
when the plugin's Lua side can't find the dep — clearer than a
validator warning naming a field whose absence the user may not care
about.

Also removes the two validate_test.lua tests that pinned the warning,
and the corresponding bullet from docs/tips.md.

Decision pinned in .claude/review-decisions.md as
"Unsupported lazy.nvim spec fields are silently accepted".
The validator accepts defaults.version as {string, table, boolean} so
that defaults.version=false (the documented no-default opt-out) passes.
But normalize_version's fallback guard was `default_version ~= nil and
default_version ~= false`, which let defaults.version=true through and
propagated the boolean into vim.pack.add's spec table — surfacing as a
less-obvious downstream error.

Tighten normalize_version to accept only string|table values for the
defaults.version fallback. Both true and false now silently no-op,
matching how spec-level `version = true` already falls through the
if-elseif chain as a silent no-op. The validator stays type-lenient
because vim.validate's flat type list can't express "string|table OR
exactly false"; the runtime guard does the precise filtering.

Adds two regression tests in version_test.lua: defaults.version=true
must not leak to vim.pack.add, and defaults.version=false must not
clobber an explicit per-spec version.

Decision pinned in .claude/review-decisions.md as
"defaults.version: both true and false are silent no-ops".
…triggers

is_lazy_by_triggers_or_floor conflated two unrelated concerns: the
spec-independent defaults.lazy short-circuit and the spec-dependent
trigger resolution. Once default_lazy() is hoisted into M.is_lazy
between the explicit-lazy guard and the trigger check, has_lazy_parent
is only reached when default_lazy() is false — so the inner
default_lazy() check at that site becomes dead. has_lazy_parent now
answers a single, narrower question: "does any parent have an explicit
lazy=true or a lazy trigger?"

Renames the helper to has_lazy_triggers — accurate and matches the
is_dependency_only naming pattern in the same file.

Behavior-preserving (501/0 busted). The lazy_parent_cache invariant is
unchanged; every yes-path still caches.

Also adds two regression tests in conditional_test.lua:
- pin has_lazy_parent inheritance via a triggered parent (the existing
  defaults.lazy=true test short-circuited via default_lazy() before
  reaching has_lazy_parent, so the inheritance path was effectively
  un-pinned);
- pin that enabled=false wins over defaults.lazy=true (the prune layer
  runs upstream of is_lazy).
docs/tips.md gained an "enabled vs cond" bullet and a mention of
defaults.lazy=true in the default-lazy bullet during the lazy-parity
work, but the parallel §11 MIGRATING FROM LAZY.NVIM section in
doc/zpack.txt was not updated.

Mirrors both: the default-lazy entry now points at defaults.lazy=true
as an alternative to per-spec lazy=true, and a new
zpack-migration-enabled-cond entry explains the enabled/cond split
that lazy.nvim collapses.
@zuqini zuqini merged commit d52f0dd into main May 27, 2026
4 checks passed
@zuqini zuqini deleted the feat/lazy-nvim-parity-defaults-and-validation branch May 27, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant