Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 38 additions & 1 deletion .backlog/CTLD-TOOLS-TUI/PRD.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Status: ready
Status: done

# PRD — CTLD-TOOLS-TUI

Expand Down Expand Up @@ -139,3 +139,40 @@ suite; dcs-bridge's `test_client_tui.py` for the textual `app.run_test()` + Pilo
- The embedded-reference shift benefits the whole tool (usable without `src/`), not just the TUI, and
is the prerequisite ticket.
- Only ctld-tools roadmap item after this: none — `modTypes`/companion is the follow-up lot above.

## Amendment (2026-07-21, post-first-run review)

After trying the TUI, four enhancements were agreed and folded into the same lot/PR:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (typo): The phrase "four enhancements" conflicts with the twelve numbered items that follow.

The introduction says "four enhancements" but the list is numbered 1–12. Please adjust the wording to match the actual count (or use a neutral phrase like "several enhancements"), or restructure the list so the number and text are consistent.

Suggested change
After trying the TUI, four enhancements were agreed and folded into the same lot/PR:
After trying the TUI, several enhancements were agreed and folded into the same lot/PR:


1. **i18n (EN + FR)** following the OS language, forced by `--lang` / `CTLD_LANG` — modelled on VMCT
(stdlib layer, flat JSON catalogs). Covers the TUI **and** the validation findings.
2. **Undo / redo** in the edit model (snapshot stack), bound to Ctrl+Z / Ctrl+Y.
3. **Delete a tree entry** (with confirmation) — distinct from the catalogue-level `remove`.
4. **Patch troop group** — adds a `ctld.patchTroopGroup` runtime helper (this **intentionally
extends the runtime API**, superseding the "no runtime API change" line above for this one
symmetric helper) and a 3-button **Add / Remove / Patch → type chooser** ergonomics.
5. **Settings help** — Set setting is a filterable picker over the ~108 scalar settings, showing and
pre-filling each default; unknown settings are flagged (warning). The embedded reference bundle
gains `scalarSettings` (name → default).
6. **Value pickers for settings** — booleans and fixed-value settings are chosen from a list (not
typed). Allowed values come from a new **authoring schema** `src/CTLD_config_schema.yaml`
(additive, NOT consumed by the build — keeps gen-config/extract/parity untouched), folded into
the embedded bundle by `gen-reference`. This file is also the intended home for the per-setting
**descriptions** (the earlier roadmap item), so that follow-up needs no new plumbing.
7. **Unsaved-changes guard** — the edit model tracks a `dirty` flag; quitting the TUI while dirty
asks for confirmation and reports how long ago the last save was.
8. **Default value highlighted** — in Set setting, the default is shown bold in the label and the
default option is marked "(default)" in the bool/enum lists.
9. **Fixed file names + auto-load** — Save/Generate no longer prompt for a path (canonical
`user-config.yaml` / `CTLD_userConfig.lua`); the TUI auto-loads `user-config.yaml` on start if it
exists. Only Inject prompts (the `.miz`).
10. **Edit a tree entry** (`e`) — reopens the matching form pre-filled with the current values and
updates the entry in place (via `EditModel.update_entry`), so a mistake (e.g. a crate added
without a name) can be corrected instead of deleted and re-entered. Covers all six entry kinds.
11. **Weight uniqueness on patch** — a `patch` that changes a crate's weight is validated for
collision (excluding the target's own current weight), and `gen-user` maps the patch's
`weight_kg` to the runtime `weight` key. Uniqueness on `add` was already covered (now also
locked by a test for two same-weight adds).
12. **File browser for inject** — Inject opens a `DirectoryTree` modal filtered to `.miz` files
(rooted at the user-config's folder) instead of a free-text path prompt. `PathPrompt` removed
(Save/Generate use fixed names).
2 changes: 1 addition & 1 deletion .backlog/CTLD-TOOLS-TUI/tickets/01-embedded-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 01 — Embedded reference catalogue (lupa build-time-only)

Status: 🧑 planned
Status: ✅ done
Type: AFK
Repo: CTLD

Expand Down
2 changes: 1 addition & 1 deletion .backlog/CTLD-TOOLS-TUI/tickets/02-edit-model.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 02 — User-config edit model (pure, testable)

Status: 🧑 planned
Status: ✅ done
Type: AFK
Repo: CTLD

Expand Down
2 changes: 1 addition & 1 deletion .backlog/CTLD-TOOLS-TUI/tickets/03-filter-picker.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 03 — Filterable picker (filter logic + widget)

Status: 🧑 planned
Status: ✅ done
Type: AFK
Repo: CTLD

Expand Down
2 changes: 1 addition & 1 deletion .backlog/CTLD-TOOLS-TUI/tickets/04-textual-tui.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 04 — Textual TUI app (the console)

Status: 🧑 planned
Status: ✅ done
Type: AFK
Repo: CTLD

Expand Down
2 changes: 1 addition & 1 deletion .backlog/CTLD-TOOLS-TUI/tickets/05-cli-and-packaging.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 05 — `tui` command + packaging (exe without lupa)

Status: 🧑 planned
Status: ✅ done
Type: AFK
Repo: CTLD

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 06 — Docs, CHANGELOG, index

Status: 🧑 planned
Status: ✅ done
Type: AFK
Repo: CTLD

Expand Down
2 changes: 1 addition & 1 deletion .backlog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ authored **per lot, when the lot is started** (not in batch).
| `FEAT-MOVING-ZONE` | ✅ merged (PR #49) | Resolve CTLD zone positions lazily via `trigger.misc.getZone()` so zones attached to a DCS Moving Zone follow their anchor unit. Covers all zone types (LGZ_, TRZ_, AIZ_, WPZ_), polygon support, anchor-death guard via `isAlive()`, `getCenter()` unified across all zone types. | `feature/feat-moving-zone` |
| `FEAT-USERCONFIG-API` | ✅ merged (PR #45) | Replace broken Section 2 of `CTLD_userConfig.lua` with a safe MM API (`ctld.userSetup` callbacks + helpers), relocate `injectAACrates` to bootstrap, fix all parity bugs in userConfig template. | `feature/userconfig-api` |
| `CTLD-TOOLS-CONFIG` | ✅ merged (PR #46) | Lot 2 of `ctld-tools` ([ADR 0009](../dev/adr/0009-external-yaml-authoring-ctld-tools.md)): engine defaults moved out of `CTLD_config.lua` into `src/CTLD_config.yaml` (source of truth, sectioned mm-facing/advanced); isolated poetry package `tools/ctld-tools` (typer/ruamel/lupa, VMCT stack) with `extract` + `gen-config`; committed generated `CTLD_config_defaults.lua` + parity/drift guards + `python-quality` CI. `load()` copies `ctld.__configDefaults`. | `feature/ctld-tools-config` |
| `CTLD-TOOLS-TUI` | ⬜ ready | Interactive **textual** TUI for `ctld-tools` ([ADR 0009](../dev/adr/0009-external-yaml-authoring-ctld-tools.md)): a MM console that structurally edits the `user-config.yaml` (4 sections), validates live, and generates/injects — all in one screen, with filter-as-you-type pickers (DCS types, catalogue). **Embedded reference** (catalogue bundled from `src/`) makes `--src` optional and moves **lupa build-time-only** (exe drops it). Model separated from UI for pure unit tests + Pilot smoke. modTypes/companion out of scope (separate lot). | `feature/ctld-tools-tui` |
| `CTLD-TOOLS-TUI` | ✅ merged (PR #52) | Interactive **textual** TUI for `ctld-tools` ([ADR 0009](../dev/adr/0009-external-yaml-authoring-ctld-tools.md)): a MM console that structurally edits the `user-config.yaml`, validates live, and generates/injects — all in one screen, with filter-as-you-type pickers, add/remove/patch + edit/delete of entries, undo/redo, settings help (defaults + bool/enum lists via `src/CTLD_config_schema.yaml`), unsaved-changes guard and a `.miz` file browser. **Embedded reference** (bundled from `src/`) makes `--src` optional and moves **lupa build-time-only** (exe drops it). **i18n EN+FR** (OS locale + `--lang`). Runtime gains `ctld.patchTroopGroup`. Model separated from UI for pure unit tests + Pilot smoke. modTypes/companion out of scope (separate lot). | `feature/ctld-tools-tui` |
| `CTLD-TOOLS-MIZ-INJECT` | ✅ merged (PR #50) | `ctld-tools inject`: insert the generated `CTLD_userConfig.lua` into a `.miz` as a rank-1 MISSION START trigger (idempotent), patching `trig`/`trigrules` with index-shift + in-code `[idx]` rewrite (VMCT approach). Vendored `luadata`. Round-trip tested (valid Lua, single after re-inject); final validation = DCS load. | `feature/ctld-tools-miz-inject` |
| `CTLD-TOOLS-FINALIZE` | ✅ merged (PR #48) | Finalize `ctld-tools`: **gen-au-build** (`merge_CTLD.ps1` regenerates `CTLD_config_defaults.lua` via `ctld-tools`, now a git-ignored artifact; CI/release gain setup-python; drift check dropped) + build & attach **`ctld-tools.exe`** (separate isolated job, verified) + dedicated MM doc page `ctld-tools.md` (EN+FR). | `feature/ctld-tools-finalize` |
| `CTLD-TOOLS-USERCONFIG` | ✅ merged (PR #47) | Lot 3 of `ctld-tools` ([ADR 0009](../dev/adr/0009-external-yaml-authoring-ctld-tools.md)): MM volet — `validate` (user-config.yaml against the reference + datamine, clear report with suggestions) + `gen-user` (compile add/remove/patch ops into `CTLD_userConfig.lua` calling the `ctld.userSetup` helpers, targeting crates/troops **by name**) + `gen-user --scaffold`; embedded `dcs_types.json`; `ctld-tools.exe` attached to Releases (isolated). e2e-tested. | `feature/ctld-tools-userconfig` |
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ jobs:
shell: bash
run: |
pip install poetry
poetry install --without dev --without build
# gen-config needs lupa, a build-time dep in the dev group; keep dev, drop build.
poetry install --without build
- name: Build CTLD.lua
run: powershell -ExecutionPolicy Bypass -File tools\build\merge_CTLD.ps1
shell: pwsh
Expand Down Expand Up @@ -115,7 +116,8 @@ jobs:
working-directory: tools/ctld-tools
run: |
pip install poetry
poetry install --without dev --without build
# gen-config needs lupa, a build-time dep in the dev group; keep dev, drop build.
poetry install --without build
poetry run ctld-tools gen-config --yaml ../../src/CTLD_config.yaml --out ../../src/CTLD_config_defaults.lua
- name: Install Lua + LuaRocks + busted + luacov
run: |
Expand Down
26 changes: 20 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ jobs:
shell: bash
run: |
pip install poetry
poetry install --without dev --without build
# gen-config needs lupa, a build-time dep in the dev group; keep dev, drop build.
poetry install --without build

- name: Build CTLD.lua
run: powershell -ExecutionPolicy Bypass -File tools\build\merge_CTLD.ps1
Expand Down Expand Up @@ -73,9 +74,12 @@ jobs:
shell: pwsh

# ─────────────────────────────────────────────────────────────
# Build the Mission Maker tool ctld-tools.exe (PyInstaller, lupa + datamine
# bundled) and attach it to the release. Separate job: if packaging fails, the
# CTLD.lua release (job above) is already published — only the exe is missing.
# Build the Mission Maker tool ctld-tools.exe (PyInstaller) and attach it to the
# release. The exe bundles textual (the TUI), the embedded reference bundle and the
# DCS type set — but NOT lupa: the reference is committed data resolved without it,
# so we install without the dev group (lupa lives there) and exclude the module.
# Separate job: if packaging fails, the CTLD.lua release (job above) is already
# published — only the exe is missing.
# ─────────────────────────────────────────────────────────────
build-exe:
name: Build & attach ctld-tools.exe
Expand All @@ -93,11 +97,21 @@ jobs:
shell: bash
run: |
pip install poetry
poetry install --with build
# --without dev drops lupa (build-time only); --with build adds pyinstaller.
poetry install --without dev --with build
poetry run pyinstaller --onefile --name ctld-tools \
--collect-all lupa \
--collect-all textual \
--exclude-module lupa \
--add-data "ctld_tools/data/dcs_types.json;ctld_tools/data" \
--add-data "ctld_tools/data/reference.json;ctld_tools/data" \
--add-data "ctld_tools/data/locales;ctld_tools/data/locales" \
ctld_tools/__main__.py
- name: Smoke-check the exe (lists the tui command, runs without lupa)
working-directory: tools/ctld-tools
shell: bash
run: |
./dist/ctld-tools.exe --help | grep -q " tui " || { echo "::error::tui command missing from exe"; exit 1; }
./dist/ctld-tools.exe tui --help | grep -qi "interactive" || { echo "::error::tui --help failed"; exit 1; }
- name: Attach ctld-tools.exe to the release
shell: bash
env:
Expand Down
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,47 @@ Versioning follows [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Tooling — ctld-tools: interactive TUI + embedded reference (CTLD-TOOLS-TUI)

- **`ctld-tools tui`**: a full-screen **textual** console for Mission Makers — a structured editor of
the `user-config.yaml` (settings / crates / troops / arrays) with **filter-as-you-type pickers**
(DCS types, catalogue crates/troops), **live validation**, and **save / generate / inject** in one
place. Generation is refused while any validation error remains.
- **Actions**: three buttons — **Add / Remove / Patch** — each followed by a type chooser (only the
valid object kinds), then a guided form. **Edit** a tree entry (`e`) reopens its form pre-filled to
fix it in place; **delete** (with confirmation); **undo / redo** (Ctrl+Z / Ctrl+Y). **Patch** now
works on troop groups too.
- **Settings help**: Set setting picks from the ~108 scalar settings via a filterable picker, showing
and pre-filling each setting's default; an unknown setting is flagged (warning, with a suggestion).
The embedded reference bundle now carries the scalar settings and their defaults. Boolean and
fixed-value settings are chosen from a **list** (true/false, or an enum such as `JTAC_lock`); the
allowed values come from a new authoring schema `src/CTLD_config_schema.yaml` (additive, not used
by the build), folded into the embedded bundle.
- **Unsaved-changes guard**: quitting the TUI with unsaved edits asks for confirmation and shows how
long ago the last save was.
- **Crate weight uniqueness on patch**: validation now also flags a `patch` that re-weights a crate
onto an already-used weight (previously only `add` was checked); `gen-user` maps a patch's
`weight_kg` to the runtime `weight` key, as `add` already did.
- **Fixed file names**: Save always writes the same `user-config.yaml` and Generate the canonical
`CTLD_userConfig.lua` beside it (no path prompt); the TUI **auto-loads** `user-config.yaml` on
start if it exists. Inject opens a **file browser** (DirectoryTree filtered to `.miz`) to pick the
mission.
- **Internationalisation (EN + FR)**: the TUI and the validation messages follow the **OS language**;
force it with `--lang` or `CTLD_LANG`. Tiny stdlib layer (flat JSON catalogs, `data/locales/`),
modelled on VMCT.
- **Runtime**: new `ctld.patchTroopGroup(name, patch)` helper in `CTLD_userSetup.lua` (mirrors
`patchCrate`), so a troop group can be patched by name from the `user-config`.
- **Embedded reference**: the catalogue is now bundled in the tool (`ctld_tools/data/reference.json`,
generated from `src/` by the new `gen-reference` build step and committed, golden-tested for
parity). `Reference.from_embedded()` is the default for `validate` / `gen-user` / `tui`, so the MM
needs **only the `.exe`** — no CTLD `src/`. `--src` stays as a dev override.
- **lupa is now build-time-only** (moved to the `dev` group, imported lazily): the MM `.exe` ships
without lupa or the native Lua binary. Only `gen-reference` / `gen-config` / `extract` use it.
- Edit logic (`EditModel`) and the picker filter are pure modules, unit-tested independently of the
textual UI; a Pilot smoke test proves the UI↔model wiring. See [ADR 0009](dev/adr/0009-external-yaml-authoring-ctld-tools.md).
- Docs: `mission-maker/ctld-tools.md` (EN + FR) gains the interactive-editor section and the
embedded-reference note (`--src` no longer required).

### Tooling — ctld-tools: automatic `.miz` injection (CTLD-TOOLS-MIZ-INJECT)

- **`ctld-tools inject`**: inserts a generated `CTLD_userConfig.lua` into a `.miz` as a **MISSION
Expand Down
30 changes: 29 additions & 1 deletion CTLD.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
--[[
CTLD.lua - Combined Transport and Logistics Dispatcher for DCS World
Version : 2.0.0-rc1
Built : 2026-07-20
Built : 2026-07-21
Source : https://github.com/VEAF/CTLD
Licence : MIT
DO NOT EDIT - generated by tools/build/merge_CTLD.ps1
Expand Down Expand Up @@ -529,6 +529,34 @@ function ctld.removeTroopGroup(name)
return false
end

--- Patch an infantry group template found by name. Shallow merge at the top level,
-- plus a one-level deep merge for table-valued fields — mirrors patchCrate, so a
-- single field can be changed without rewriting the whole group.
---@param name string
---@param patch table
---@return boolean patched
function ctld.patchTroopGroup(name, patch)
local groups = _settings()["loadableGroups"]
if type(groups) == "table" then
for _, g in ipairs(groups) do
if g.name == name then
for k, v in pairs(patch or {}) do
if type(v) == "table" and type(g[k]) == "table" then
for kk, vv in pairs(v) do
g[k][kk] = vv
end
else
g[k] = v
end
end
return true
end
end
end
ctld.logWarning("ctld.patchTroopGroup: no group named '%s' — nothing patched", tostring(name))
return false
end

--- Append an entry to an array-type setting (transportPilotNames, troopZones,
-- wpZones, extractableGroups, logisticUnits, ...).
---@param settingName string
Expand Down
16 changes: 16 additions & 0 deletions dev/adr/0009-external-yaml-authoring-ctld-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,19 @@ round-trip is feasible provided the generator re-emits those wrappers by convent
`gen-user`, `.exe` distribution). Exact filenames pinned per PRD.
- ADR 0008's `logDefaults` / schema-comments become power-user/debug aids rather than the primary
MM path; its scope is not reduced.

## Note — embedded reference & lupa build-time-only (CTLD-TOOLS-TUI, 2026-07-21)

The interactive TUI (deferred scope point 4) requires the tool to run **without `src/`** — a
non-dev MM only has the `.exe`. This shifts how the reference catalogue is resolved:

- A build step **`gen-reference`** (lupa) freezes the catalogue slice the runtime needs
(`spawnableCrates` with AA injected, `loadableGroups`) into a JSON bundle,
`ctld_tools/data/reference.json`, **committed** and kept in sync with `src/` by a golden test —
the same pattern as the embedded `dcs_types.json`.
- `Reference.from_embedded()` reads the bundle and becomes the **default** for `validate`,
`gen-user` and the TUI; `--src` → `from_src` stays as a dev override.
- **lupa moves to build-time only** (the `dev` dependency group). The runtime path
(TUI / `validate` / `gen-user` via the embedded reference) no longer imports it, so the MM `.exe`
ships without lupa or the native Lua binary. Only `gen-reference` / `gen-config` / `extract`
(build steps) use lupa. lupa imports are lazy so importing a runtime module never pulls it in.
11 changes: 10 additions & 1 deletion dev/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ Contexte (émergé du grill TUI) : deux points liés.
rattrapé par le test-en-DCS. `logDefaults` reste (debug power-user). Décision David (2026-07-20) :
pas maintenant, à faire en lot séparé.

<!-- ctld-tools — mode TUI interactif : formalisé en lot `.backlog/CTLD-TOOLS-TUI/` (grill-with-docs, 2026-07-20). -->
<!-- ctld-tools — mode TUI interactif : livré (lot CTLD-TOOLS-TUI, PR #52). -->

## ctld-tools — descriptions des settings dans la TUI

Le picker « Set setting » de la TUI affiche le nom et la **valeur par défaut** des ~108 settings, mais
pas de **description**. Le fichier `src/CTLD_config_schema.yaml` (ajouté au lot TUI pour les `choices`)
est prévu pour accueillir une clé `description:` par setting — la plomberie existe déjà (chargée dans
le bundle embarqué). Candidat lot : rédiger ces descriptions (EN+FR), idéalement **extraites de la doc
mission-maker existante** (`configuration.md`) plutôt que réinventées. Décision David (2026-07-21) :
hors périmètre du lot TUI, à traiter séparément.

<!-- DEV-LOCAL-MIZ — formalisé en lot `.backlog/DEV-LOCAL-MIZ/` (grill-with-docs, 2026-07-19). -->

Expand Down
Loading