Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
f4b3493
feat: add unrealpak footer+index reader (#136)
dyoung522 Jul 31, 2026
b472685
feat: add unrealpak file content reading (#136)
dyoung522 Jul 31, 2026
eab7e10
feat: add unrealpak writer (#136)
dyoung522 Jul 31, 2026
70e2838
test: add unrealpak writer/reader round-trip coverage (#136)
dyoung522 Jul 31, 2026
d524f64
feat: add Firestore typed-value decoder for Icarus source (#136)
dyoung522 Jul 31, 2026
24c9b51
feat: add Firestore REST client for Icarus source (#136)
dyoung522 Jul 31, 2026
a673cea
feat: implement Icarus ModSource over Firestore REST (#136)
dyoung522 Jul 31, 2026
4d16cbf
feat: register Icarus as a built-in mod source (#136)
dyoung522 Aug 1, 2026
dab5017
feat: add .EXMOD parsing and row-patch application (#136)
dyoung522 Aug 1, 2026
4e20ce6
feat: add .EXMODZ archive unpacking (#136)
dyoung522 Aug 1, 2026
e25e236
feat: fetch Icarus base data tables from the per-week community dump …
dyoung522 Aug 1, 2026
6b22ec6
fix: distinguish unexpected pak read errors from Oodle skip in valida…
dyoung522 Aug 1, 2026
ce4d2f4
feat: implement exmod compile orchestration (#136)
dyoung522 Aug 1, 2026
91198e9
fix: remove partial output pak on mid-compile failure (#136)
dyoung522 Aug 1, 2026
9ec6978
feat: wire exmod compile step into cache-population pipeline (#136)
dyoung522 Aug 1, 2026
74f3af5
fix: gate exmod compile branch on file, not just game (#136)
dyoung522 Aug 1, 2026
2578b37
docs: add CHANGELOG entry for Icarus mod support (#136)
dyoung522 Aug 1, 2026
6cac5bd
chore: silence errcheck on intentional Close defers (#136)
dyoung522 Aug 1, 2026
4d905f5
fix: harden size validation, token escaping, and writer cleanup (revi…
dyoung522 Aug 1, 2026
8b282f6
fix: guard writer index overflow, cap dump tar reads, fix filename fa…
dyoung522 Aug 1, 2026
e18b0bd
fix: normalize exmodz entry matching, drop dead cacheDir, guard int w…
dyoung522 Aug 1, 2026
1c261fe
fix: sort Icarus search results before paginating (review) (#136)
dyoung522 Aug 1, 2026
e03497c
docs: correct SetDataDir comment after cacheDir removal (#136)
dyoung522 Aug 1, 2026
49f1784
Merge pull request #171 from DonovanMods/dyoung522/icarus-136
dyoung522 Aug 1, 2026
d562d33
feat: parse the pak footer's CompressionMethods table (#175)
dyoung522 Aug 1, 2026
56a5e2d
feat: decompress Zlib pak entries with the standard library (#175)
dyoung522 Aug 1, 2026
8c5969d
feat: compile Icarus mods from the installed pak, drop the dump subsy…
dyoung522 Aug 1, 2026
8ead120
refactor: drop the data_dump_path setting and SetDataDir wiring (#175)
dyoung522 Aug 1, 2026
d400834
docs: drop data_dump_path, document compiling from the installed pak …
dyoung522 Aug 1, 2026
ef687fd
fix: apply .EXMOD rows to the real DataTable schema as upserts (#175)
dyoung522 Aug 1, 2026
f7ff41c
Merge pull request #176 from DonovanMods/dyoung522/175-zlib-pivot
dyoung522 Aug 1, 2026
6915cf7
fix: mount compiled Icarus paks at the game data loader path (#178)
dyoung522 Aug 1, 2026
f8526fc
Merge pull request #179 from DonovanMods/dyoung522/fix-178-mount-point
dyoung522 Aug 1, 2026
0a42056
feat: auto-detect Icarus in lmm game detect (#177)
dyoung522 Aug 1, 2026
6d43df2
docs: align auto-detect docs/test naming with value-equivalence (revi…
dyoung522 Aug 1, 2026
b1a3798
Merge pull request #180 from DonovanMods/dyoung522/177-icarus-autodetect
dyoung522 Aug 1, 2026
f45e6fb
fix: cap and bound per-entry zip reads in .EXMODZ archives (security,…
dyoung522 Aug 1, 2026
551d13f
fix: wrap manifest-parse errors, drain Firestore error bodies (#136)
dyoung522 Aug 1, 2026
4c2ede9
test: broaden defensive and API coverage sweep (#136)
dyoung522 Aug 1, 2026
3d30637
fix: clamp search pagination overflow, correct stale base-pak comment…
dyoung522 Aug 1, 2026
20c1c33
Merge pull request #182 from DonovanMods/dyoung522/icarus-hardening
dyoung522 Aug 1, 2026
1b7afb3
fix: record deployed state on mod disable/enable (#183)
dyoung522 Aug 1, 2026
e0cdf58
fix: seed Deployed=true in the undeploy-failure test (#183 review)
dyoung522 Aug 1, 2026
b2dcec2
fix: self-heal stale deployed flag on already-disabled mods (#183)
dyoung522 Aug 1, 2026
bdd8efc
Merge pull request #184 from DonovanMods/dyoung522/fix-183-disable-de…
dyoung522 Aug 1, 2026
0e8a31e
chore: merge develop (#183 deployed-flag fix) into epic
dyoung522 Aug 1, 2026
87a6286
Merge pull request #181 from DonovanMods/epic/icarus-136
dyoung522 Aug 1, 2026
59d80c0
feat: add {category}/{tags} placeholders to custom api search endpoints
dyoung522 Aug 1, 2026
1f4c2ce
fix: fail loud on unknown link_method/deploy_mode values (#172)
dyoung522 Aug 1, 2026
f720ce2
fix: route local .exmodz imports through the compile pipeline (#173)
dyoung522 Aug 1, 2026
c88ad1a
Merge remote-tracking branch 'origin/develop' into dyoung522/120-sear…
dyoung522 Aug 1, 2026
ec823df
Merge branch 'develop' into dyoung522/172-failloud-config
dyoung522 Aug 1, 2026
ccd7e68
feat: colorize CLI output by default on a TTY (#112)
dyoung522 Aug 1, 2026
4a8bacb
fix: include compile in deploy_mode validation message (#172 review)
dyoung522 Aug 1, 2026
64939d2
Merge remote-tracking branch 'origin/develop' into dyoung522/112-cli-…
dyoung522 Aug 1, 2026
04102ec
docs: correct deploy-mode comment, wrap detect error (#172 review)
dyoung522 Aug 1, 2026
c50de32
fix: honor empty NO_COLOR per presence-only spec (#112 review)
dyoung522 Aug 1, 2026
bfdf021
fix: atomic cache commit + accurate compiler error on import (#173 re…
dyoung522 Aug 1, 2026
8e29bc4
Merge pull request #187 from DonovanMods/dyoung522/173-import-compile
dyoung522 Aug 1, 2026
c113adb
chore: merge develop into 120-search-placeholders pre-merge
dyoung522 Aug 1, 2026
a88f101
chore: merge develop into 172-failloud-config pre-merge
dyoung522 Aug 1, 2026
82e6147
chore: merge develop into 112-cli-colors pre-merge
dyoung522 Aug 1, 2026
5f97137
Merge pull request #185 from DonovanMods/dyoung522/120-search-placeho…
dyoung522 Aug 1, 2026
b7e9d1e
chore: sync develop pre-merge
dyoung522 Aug 1, 2026
63ceffc
Merge pull request #186 from DonovanMods/dyoung522/172-failloud-config
dyoung522 Aug 1, 2026
395dcf1
chore: sync develop pre-merge
dyoung522 Aug 1, 2026
228bfaf
Merge pull request #188 from DonovanMods/dyoung522/112-cli-colors
dyoung522 Aug 1, 2026
875daac
fix: announce compile step instead of generic Extracting message (#190)
dyoung522 Aug 1, 2026
666a262
fix: GetEffectiveLinkMethod surfaces invalid link_method instead of s…
dyoung522 Aug 1, 2026
f075222
fix: name blank CompressionMethods slots in unrealpak errors (#190)
dyoung522 Aug 1, 2026
c523530
fix: dedup symlinked Steam roots so detect only scans once (#190)
dyoung522 Aug 1, 2026
717b0f7
fix: remove now-empty per-mod cache dir after last version deleted (#…
dyoung522 Aug 1, 2026
2ffa683
fix: drop duplicated error wrap in status (#189 review)
dyoung522 Aug 1, 2026
96091e2
Merge pull request #191 from DonovanMods/dyoung522/189-effective-link…
dyoung522 Aug 1, 2026
c070ff2
chore: sync develop pre-merge
dyoung522 Aug 1, 2026
1eb7006
Merge pull request #192 from DonovanMods/dyoung522/190-polish-sweep
dyoung522 Aug 1, 2026
6b89149
feat: richer default CLI color palette (#193)
dyoung522 Aug 1, 2026
1d73584
Merge pull request #194 from DonovanMods/dyoung522/193-richer-palette
dyoung522 Aug 1, 2026
199f3ac
fix: unify list row-tint coloring across verbose and plain output (#193)
dyoung522 Aug 1, 2026
a3afd3a
test: prove TTY gate, not --no-color, keeps piped list plain (#193 re…
dyoung522 Aug 1, 2026
c0ca7af
Merge pull request #195 from DonovanMods/dyoung522/193-list-consistency
dyoung522 Aug 1, 2026
8aeb952
feat: record base-pak fingerprint at compile time, detect staleness (…
dyoung522 Aug 1, 2026
50c4b86
feat: recompile a stale compile in place, lock/pin-aware (#196)
dyoung522 Aug 1, 2026
b470a0b
feat: wire base-pak recompile through lmm update (#196)
dyoung522 Aug 1, 2026
75bffeb
feat: lmm verify warns on stale compiles (#196)
dyoung522 Aug 1, 2026
7873351
feat: TUI update screen parity for base-pak recompile (#196)
dyoung522 Aug 1, 2026
f33257e
docs: CHANGELOG entry for base-pak recompile (#196); trunk errcheck fix
dyoung522 Aug 1, 2026
74d446f
fix: stat-error handling and filename sanitization in recompile (#196…
dyoung522 Aug 1, 2026
fb3a211
fix: sanitize source filenames at every temp-path join (#196 review)
dyoung522 Aug 1, 2026
541b485
Merge pull request #198 from DonovanMods/dyoung522/196-base-staleness
dyoung522 Aug 1, 2026
c0d829b
feat: MergeCompiler interface + Icarus merge engine (#197)
dyoung522 Aug 2, 2026
c2b5810
feat: download path ingests .exmodz as validate+retain, no per-mod pa…
dyoung522 Aug 2, 2026
b8f8638
feat: import path ingests .exmodz as validate+retain, no per-mod pak …
dyoung522 Aug 2, 2026
24cd38a
feat: MergedFingerprint type + Service.syncMergedPak regenerate engin…
dyoung522 Aug 2, 2026
79f92bb
feat: sync merged pak on enable/disable/uninstall/deploy/switch/updat…
dyoung522 Aug 2, 2026
4cc3ade
feat: CheckMergedPakStaleness + ApplyMergedPakRegen, retire per-mod #…
dyoung522 Aug 2, 2026
a497ab9
feat: cmd/lmm targets merged-pak regen/staleness instead of per-mod r…
dyoung522 Aug 2, 2026
e4d6e21
feat: internal/tui targets merged-pak regen/staleness instead of per-…
dyoung522 Aug 2, 2026
a564538
chore: remove dead #196 per-mod compile fingerprint machinery (#197)
dyoung522 Aug 2, 2026
9f49214
test: pin locked-mod-does-not-block-merge semantics (#197, coordinato…
dyoung522 Aug 2, 2026
b8ae4e7
docs: amend #196 CHANGELOG entry for merged-pak compilation (#197)
dyoung522 Aug 2, 2026
24f47a0
fix: imported .exmodz mods now participate in merges (C1, #197 final …
dyoung522 Aug 2, 2026
be11ce8
fix: sync merged pak on rollback; purge undeploys it explicitly (I1/I…
dyoung522 Aug 2, 2026
e9d31b1
fix: sync merged pak on profile import and scan-mode import (I3, #197…
dyoung522 Aug 2, 2026
60c1400
fix: verify no longer reports FILE COUNT MISMATCH for healthy exmodz …
dyoung522 Aug 2, 2026
7893026
fix: detect and self-heal a merged pak whose fingerprint matches but …
dyoung522 Aug 2, 2026
c20950a
fix: M1/M3/M4 minors from #197 final review
dyoung522 Aug 2, 2026
59f430b
Merge pull request #199 from DonovanMods/dyoung522/197-merged-pak
dyoung522 Aug 2, 2026
1a633c9
fix: batchInstallMods (multi-select install) now syncs the merged pak…
dyoung522 Aug 2, 2026
ee028dc
fix: sync merged pak on profile apply/sync, mod edit, and verify --fi…
dyoung522 Aug 2, 2026
6fcb4c8
fix: make merged-pak sync failures loud across every flow (#197)
dyoung522 Aug 2, 2026
94a34a7
fix: install/import UX wording and stale-reason accuracy for merged p…
dyoung522 Aug 2, 2026
b7c0254
fix: thread merged-pak sync outcome into install messages; fix --fix …
dyoung522 Aug 2, 2026
e508177
Merge pull request #200 from DonovanMods/dyoung522/197-postsmoke
dyoung522 Aug 2, 2026
00802cf
feat: document merge precedence; display list in profile load order (…
dyoung522 Aug 2, 2026
a27cc48
fix: correct field-vs-row and load-order wording (#201 review)
dyoung522 Aug 2, 2026
d35c56f
Merge pull request #202 from DonovanMods/dyoung522/201-list-order-docs
dyoung522 Aug 2, 2026
4e41083
docs: archive completed Icarus epic plans (#136, #175, #197)
dyoung522 Aug 2, 2026
dc7f47f
chore: bump version to 1.28.0
dyoung522 Aug 2, 2026
630d0e4
fix: gameFromDetected fails loud on missing source config (#203 review)
dyoung522 Aug 2, 2026
09f238e
fix: name the URL and a capped body snippet in getJSON errors (#203 r…
dyoung522 Aug 2, 2026
898513e
fix: cap total bundled-asset size in ParseExmodz (#203 review)
dyoung522 Aug 2, 2026
d641be4
fix: thread merged-pak staleness reason into TUI UpdateItem (#203 rev…
dyoung522 Aug 2, 2026
edc7761
fix: doList surfaces LoadProfile errors beyond ErrProfileNotFound (#2…
dyoung522 Aug 2, 2026
15602ee
fix: three Copilot round-2 findings on PR #204 (#203 release review)
dyoung522 Aug 2, 2026
c9e7fe5
Merge pull request #204 from DonovanMods/dyoung522/203-release-review…
dyoung522 Aug 2, 2026
bc661b7
chore: merge develop (release-review fixes) into release/v1.28.0
dyoung522 Aug 2, 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
29 changes: 28 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.28.0] - 2026-08-02

### Added

- CLI output is now colorized by default when stdout is a terminal, extending the existing `colorGreen`/`colorRed`/`colorYellow` accent mechanism (previously only used by `deploy`/`verify`) with a full 4-color palette (green/yellow/red/cyan, plus bold/dim) across `list`, `status`, `search`, `update`, `conflicts`, and `mod show`. Table headers are bold+cyan. `lmm list` tints the whole row identically with or without `-v` (the row-tint decision is a single shared helper keyed on the mod's actual state, not the display flag): green for the common enabled+deployed case, yellow for enabled-but-undeployed, dim for disabled. `search` tints an installed mod's whole row green; `update`'s POLICY column colors per row. `status`/`mod show` color their values, not just the odd count: `lmm status -g <game>`'s active profile and per-profile "(active)" marker are green, mod/profile counts are cyan, Link Method is cyan, Last Deploy is green (or dim when never deployed); `mod show`'s Version fields are cyan and its Update policy is colored per state (green for auto, yellow for pinned); `conflicts`' stale winner suffix is yellow; and the existing `✓`/`✗` success/failure markers extend to `update` and `mod`'s confirmation lines. Detection is TTY-aware (piped/redirected output stays plain) and layers on top of the existing `--no-color` flag and `NO_COLOR` env var (presence-only per no-color.org), which continue to work unchanged; `--json` output is never colored. Table color is applied only to already-tabwriter-padded text (accented headers, whole-row tints, or a table's genuinely last column) — never to interior cell values before they reach `text/tabwriter`, which pads columns by raw byte length and would misalign them (#112, #193)
- **Icarus built-in mod source** (`internal/source/icarus`): a public, unauthenticated Firestore-backed catalog (Project Daedalus) — `lmm search`/`install`/`update` work against it like NexusMods/CurseForge. A `.exmodz` mod file is validated and its row-level table diffs retained via a new, game-agnostic `internal/unrealpak` PAK reader/writer and the new `deploy_mode: compile` game setting (see the merged-pak bullet below for how it actually deploys); a plain `.pak` file from the same catalog is unaffected and deploys through the existing extract/copy pipeline unchanged. Base data tables are read directly from the installed game's own `data.pak`, so a merge always matches the installed game version and works entirely offline; `internal/unrealpak` reads both the stored and the Zlib-compressed entries that pak contains, using only the standard library (#136, #175)
- `lmm game detect` now recognizes Icarus (Steam App ID `1149460`) and generates a complete `games.yaml` entry for it (`deploy_mode: compile`, `sources: {icarus: icarus}`) — no more hand-editing `games.yaml` to get started. The known-games schema (`steam-games.yaml`, built-in or your own override) gained two optional fields, `deploy_mode` and `sources`, generalizing detection beyond NexusMods-only games; every existing entry is unaffected (#177)
- Custom `api` sources' `search` endpoint gains `{category}`/`{tags}` path placeholders, fed from `SearchQuery.Category`/`.Tags` (URL-escaped; multiple tags comma-joined) — previously these were silently dropped with no way for a declarative source to express category/tag filtering. A definition whose `search` path omits the new placeholders is unaffected: the values are computed but never substituted in, matching today's behavior exactly (#120)
- Compiled mods (`deploy_mode: compile`, e.g. Icarus) with more than one enabled `.exmodz` mod now compose correctly instead of silently shadowing each other: every enabled mod's table-row diffs are applied sequentially, in profile load order, into ONE merged `zzz_LMM_Merged_P.pak` per profile (named to mount last, so it always wins over a plain prebuilt `.pak`'s own table override) — two mods patching different fields of the same row, or entirely different rows of the same table, both survive; only a genuine same-field conflict is last-wins, and a bundled-asset path collision (which can't compose) is last-wins with a loud warning. This also fixes "the Friday problem" (a weekly base-pak refresh silently reverting a mod's patched tables, with nothing to notice): the merge regenerates whenever the enabled-mod set, load order, a mod's version, or the base pak itself changes. `lmm update` (CLI and TUI) reports a "recompile needed" row for the profile's merged pak (additive `--json` field `recompile_needed`/`reason`) alongside normal version updates; applying it regenerates and redeploys — pinned mods' diffs recompile normally, and a LOCKED mod's diff still participates in every merge (a lock pins that mod's own version, not the profile's merged pak). Installing/importing a `.exmodz` now only validates and retains it (a per-mod compiled pak is no longer generated or deployed); a plain prebuilt `.pak` mod, and every non-`deploy_mode: compile` game, is completely unaffected. `lmm verify` gains a matching "RECOMPILE NEEDED" warning row (`stale_compile`) for the profile's merged pak (#136, #175, #196, #197)

### Changed

- `lmm list` now displays mods in the profile's load order (the same order `lmm profile reorder` sets and the TUI's mod list already showed) instead of DB install order (`installed_at`) — the visible order is now the order that actually decides merge precedence for a `deploy_mode: compile` game. A mod installed but missing from the load order is still shown, never silently dropped, placed first (lowest priority). README and `docs/configuration.md` gain a "Merge precedence" paragraph explaining that later-in-load-order mods win conflicting table-row _fields_ (a per-field upsert; untouched fields from earlier mods survive) while bundled assets are whole-file last-wins with a warning, and that `lmm profile reorder` regenerates the merged pak immediately (#201)
- An unrecognized, non-empty `link_method` (`games.yaml`, profile files, imported profiles) or `deploy_mode` (`games.yaml`; also `lmm game detect`'s `steam-games.yaml`) is now a load-time error naming the field, the offending value, the owning game/profile, and the valid options — instead of silently falling back to the default (`symlink`/`extract`). **Breaking for configs that were already silently misbehaving:** a typo like `deploy_mode: compil` previously ran as `extract` with no warning; it now refuses to load until fixed. An empty/absent value is unaffected and keeps today's default exactly (#172)

### Fixed

- `lmm uninstall` left behind the now-empty per-mod cache directory (`<source>-<modID>/`) after removing a mod's last cached version — cosmetic litter that accumulated indefinitely. `Cache.Delete` now removes the container too, but only when it's actually empty (a mod with other cached versions, or `--keep-cache`, is unaffected) (#190)
- `lmm game detect` printed every stale-library warning twice on Linux installs where `~/.steam/steam` is a symlink to `~/.local/share/Steam` (or the reverse): both existed as candidate Steam roots, so the whole library scan — and every warning it produced — ran twice against the identical real directory. `FindSteamRoots` now dedups candidates by their resolved (symlink-followed) real path, keeping only the first; a genuinely separate second root is unaffected (#190)
- `internal/unrealpak`'s unsupported-compression-method refusal named a blank `CompressionMethods` table slot as `compression method "" (index N)` — an in-range but never-named slot, distinct from a genuinely out-of-range index. It now names the slot itself (`unnamed method N`), so the refusal is actionable instead of pointing at an empty string (#190)
- `lmm install` (and the TUI's equivalent progress line) announced a `.exmodz` compile step as "Extracting to cache..." — actively misleading, since compiling never extracts anything. It now prints "Compiling `<source>` → `<output>`..." instead, naming the actual archive and the compiled `_P.pak` output; a plain extract/copy install is unaffected and keeps today's exact "Extracting to cache..." text (#190)
- `Service.GetEffectiveLinkMethod` — the profile > game > global resolution behind every deploy/install/import/status/verify operation — no longer silently swallows an invalid profile `link_method`: since #172, `config.LoadProfile` fails loud on an unrecognized value, but `GetEffectiveLinkMethod` treated ANY profile-load error, including that one, as "no explicit override" and fell back to the game/global default with nothing surfaced — meaning a hand-edited profile with a typo'd `link_method` could deploy with the wrong method, no error, no warning. It now distinguishes that case (`errors.Is(err, domain.ErrInvalidLinkMethod)`) from a missing/unreadable profile file — which still degrades silently by design, since profiles are optional — and returns the validation error instead, propagated through every call site (`DeployProfile`, `EnableMod`/`DisableMod`, `ApplyInstall`/`ApplyUpdate`/`ApplyRollback`/`ApplyImport`/`ApplyProfileSwitch`, `lmm deploy`/`import`/`install`/`profile apply`/`verify --fix`). Narrow in practice — both save and load paths validate now, so only a profile hand-edited after the fact can trigger it — but it closes the one place #172's fail-loud contract didn't reach (#189)
- `lmm import` of a local `.exmodz` file for a `deploy_mode: compile` game (Icarus) now routes through the same compile step as a download: it resolves the game's mapped `source.Compiler`-capable source from the registry and compiles the archive against the installed base pak, caching the resulting `_P.pak` the same way `DownloadModToCache` does. Previously the import path extracted/copied `.exmodz` files as-is, landing an uncompiled archive in the cache instead of a deployable pak. A missing compiler-capable source or missing base pak now fails loud with an actionable error rather than silently caching the uncompiled file; non-`.exmodz` imports are unaffected (#173)
- `lmm mod disable` undeployed a mod's files and cleared `enabled`, but never cleared `deployed` — `lmm list -v` kept showing DEPLOYED yes after disable. The disable flow now clears `deployed` unconditionally after the undeploy attempt, even when the undeploy itself only partially succeeds (already a non-fatal, Note-reported condition), so the flag always reflects disable-intent rather than lagging behind a best-effort file cleanup. The symmetric enable path had the same gap — enabling a disabled mod re-deployed its files without ever setting `deployed` back to true — and is fixed the same way. Both `SetModDeployed` calls follow the same non-fatal Note convention already used by `DeployProfile`/`PurgeProfile` for this same setter: a failure to record the flag doesn't block the primary enable/disable outcome (#183)
- `lmm install`'s multi-select path (`batchInstallMods`, used whenever more than one search result is selected) never synced the profile's merged pak for a `deploy_mode: compile` game (Icarus): installing `.exmodz` mods this way validated and cached them but left the merged pak undeployed, silently, until a later `lmm update` self-healed it. A seam audit of every mutation entry point that can change a profile's enabled-exmodz set/order/versions found and closed 4 more gaps with the same shape — `lmm profile apply`/`profile sync`, `lmm mod edit --version`, and `lmm verify --fix` also never reached the merged-pak sync. All five now call the same shared `Service.SyncMergedPak` entry point the rest of the flows already used, instead of bypassing it. Separately, several flows that _did_ call the sync already (including single-mod `lmm install`) only recorded a failure in a diagnostic field their own CLI caller never read back — a sync failure could be completely silent, not even `--verbose`-gated. Sync failures now surface unconditionally on stderr everywhere the merged pak is synced (CLI and TUI). Also corrected three misleading messages this bug produced along the way: `lmm install`/`import` say "Installed (merged pak updated)" instead of a false "(0 files)" for a compile-mode mod (deploying zero files of its own is correct, not a failure); `lmm mod files` explains that such a mod participates in the profile's merged pak instead of suggesting it "may need to be redeployed"; and `lmm verify`'s "RECOMPILE NEEDED" row now names the real cause (a changed base pak vs. an artifact simply missing from disk) and points at `lmm update --all`, since this row is never auto-applied by a bare `lmm update` (#197)

## [1.27.1] - 2026-07-30

### Fixed
Expand Down Expand Up @@ -1156,7 +1182,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Comprehensive test coverage for core components
- MIT License

[Unreleased]: https://github.com/DonovanMods/linux-mod-manager/compare/v1.27.1...HEAD
[Unreleased]: https://github.com/DonovanMods/linux-mod-manager/compare/v1.28.0...HEAD
[1.28.0]: https://github.com/DonovanMods/linux-mod-manager/compare/v1.27.1...v1.28.0
[1.27.1]: https://github.com/DonovanMods/linux-mod-manager/compare/v1.27.0...v1.27.1
[1.27.0]: https://github.com/DonovanMods/linux-mod-manager/compare/v1.26.0...v1.27.0
[1.26.0]: https://github.com/DonovanMods/linux-mod-manager/compare/v1.25.0...v1.26.0
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,20 @@ games:
nexusmods: "starfield"
link_method: copy # This game requires file copies instead of symlinks
cache_path: /mnt/fast-ssd/starfield-mods # Store this game's mods on fast storage

icarus:
name: "Icarus"
install_path: "/path/to/Steam/steamapps/common/Icarus"
mod_path: "/path/to/Steam/steamapps/common/Icarus/Icarus/Content/Paks/mods"
sources:
icarus: "icarus"
deploy_mode: compile
```

Steam auto-detection (`lmm game detect`) knows about Icarus (App ID `1149460`) and generates an equivalent entry for you, `install_path`/`mod_path` filled in from your actual Steam library — the YAML above is kept here as reference for what gets written, not something you need to type by hand.

**Merge precedence**: with more than one `compile`-mode mod installed (currently Icarus only), the profile's load order — the same order `lmm list` displays and `lmm profile reorder` changes — decides how conflicting changes resolve. Mods are merged in load order, so a mod later in the list is applied later and wins conflicting _fields_ on a shared data-table row; it's a per-field upsert, not a whole-row overwrite, so untouched fields from earlier mods still survive. Bundled asset files can't compose that way — a same-path collision between two mods is whole-file last-wins, and installing or updating a colliding mod prints a warning naming both. Either way, the bottom of the load order has final say, and `lmm profile reorder` regenerates the merged pak immediately, so a reorder's effect on precedence is visible right away rather than at the next deploy.

### Deployment Methods

Mods can be deployed using three methods:
Expand Down Expand Up @@ -655,6 +667,8 @@ api:
| `{page}` | The internal 0-based page number, plus `page_start` (default `1`) | `search` |
| `{page_size}` | The requested page size (defaults to 20 when unspecified or ≤ 0) | `search` |
| `{offset}` | The internal 0-based page × `page_size` — independent of `page_start`, for offset-paginated APIs | `search` |
| `{category}` | The search query's category filter (source-specific ID or name), empty when unset | `search` |
| `{tags}` | The search query's tag filters, comma-joined, empty when unset | `search` |
| `{mod_id}` | The mod ID | `get_mod`, `mod_files`, `download_url` |
| `{file_id}` | The file ID | `download_url` |

Expand Down Expand Up @@ -859,6 +873,8 @@ A `directory` source now shows up with real capabilities in `lmm source list` (`
| `--no-hooks` | | Disable all hooks at runtime |
| `--no-color` | | Disable colored output (respects NO_COLOR env) |

Output is colorized by default whenever stdout is a terminal (headers, status accents like enabled/disabled/pinned, success/warning/error markers); piped or redirected output stays plain automatically, and `--json` output is never colored. Disable explicitly with `--no-color` or the `NO_COLOR` environment variable.

### Commands

| Command | Description |
Expand Down Expand Up @@ -1085,7 +1101,7 @@ The mod cache location can be customized via `cache_path` in `config.yaml`. Sett
- [x] Automatic dependency installation (opt out with `--no-deps`)
- [x] Interactive TUI (Bubble Tea) - see the Terminal UI section above
- [x] CurseForge integration
- [ ] Additional first-party built-in sources beyond NexusMods/CurseForge
- [x] Additional first-party built-in sources beyond NexusMods/CurseForge (Icarus)
- [ ] Game auto-detection beyond Steam (Lutris, Heroic, Flatpak)
- [ ] Backup and restore

Expand Down
Loading
Loading