Module bindings initialize in dependency order. A top-level let now runs after every binding its initializer actually evaluates — the ones it reads, plus everything read inside whatever it calls on the way — so a binding may reference one declared below it, in the same file or in another module, exactly as a function may call one declared later. Creating a closure evaluates nothing, so two module-level closures may still name each other freely. This kills a real miscompile: declaration order used to follow the order names happened to be listed in your imports, so a constant that depended on another could be emitted before it and crash at load with Cannot access 'X' before initialization — with nothing at compile time to warn you. v0.12.0 made the emitted JavaScript independent of import statement order; this closes the other half, the names inside a { … } brace set, so no spelling of your imports can change what your program does or the bytes it compiles to — vilan fmt can sort them freely. And a genuine cycle among initializers (including a binding that reads itself) is now a compile error that names the round trip (via A → B → A), anchored at the read that closes it and noting each participant's declaration, instead of a crash at load. The order is specified rather than incidental: spec §7.1 fixes dependency order first, then a canonical module order — the standard library first, then dependency packages, then your own, modules within a package by name, the entry file last — for bindings that depend on nothing from each other. One behavior note: a module initializer with side effects may now run in a different relative order than before — the old order was whatever your import listing happened to produce; the new one is the rule above. Bindings that actually depend on one another are unaffected: those were the broken case.
Git dependencies. A dependency can now come from a repository, pinned to an immutable point: shapes = { git = "https://…", tag = "v1.2.0" } (or rev = "<commit sha>" — exactly one; a branch is refused, because a branch moves and so cannot pin anything). The checkout is fetched shallowly, verified to be a vilan [library], and cached content-addressed under ~/.vilan/ — after the first fetch, builds are fully offline, and the cache serves the pinned content even if the upstream moves or disappears. A dependency's own git dependencies resolve through the same cache. Fetching happens only when a build of a declaring project needs it — the toolchain still makes no passive network calls, and the editor never fetches at all: a not-yet-fetched dependency shows as a note to run vilan build. Workspaces got a matching quality-of-life: [project.dependencies] declares a dependency once at the workspace root, and a member opts in with shapes = { project = true } — explicit, so nothing is inherited by surprise.
vilan.toml speaks in the editor. The manifest now has completions — keys per table, values where they're enumerable (targets, presets), quotes placed for you — and its problems finally surface where you're looking: a manifest that doesn't parse, an invalid dependency, or a git dependency that isn't fetched yet publishes a diagnostic on vilan.toml itself and clears when you fix it.
Installing vilan is becoming one command. The Homebrew tap is live today: brew install vilan-lang/vilan/vilan (macOS and Linux, both architectures). The npm package (@vilan-lang/vilan — the command is still just vilan) and the VS Code Marketplace / Open VSX listings are built and ship with the next releases as their publishing credentials land. vilan upgrade learned to respect whoever installed it: an npm- or brew-managed vilan is steered to npm update -g / brew upgrade instead of overwriting files the package manager owns — the curl-script install keeps upgrading itself exactly as before.
A save during the first watch build is never lost. vilan run --watch took its file-change baseline after the initial build, so a save landing while that build ran was silently absorbed — the watcher never noticed, and your change sat unbuilt until you saved again. The baseline now precedes the build: a save at any moment after the watcher starts triggers a round. Found because a CI test kept "flaking" — it was right, four times, on three platforms.