# Changelog

All notable changes to `dev-cockpit` are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.3] — 2026-05-12

Compose overlay lives in the state dir instead of the wrapper repo.

### Changed

- **`mount` overlay default → `<stateDir>/dev-mount-overlay.yml`** (sibling to `mount.manifest.json`). Keeps the wrapper repo clean of per-developer state, aligns the overlay's lifecycle with the manifest's, and eliminates the gitignore footgun where teammates would accidentally commit the generated file. The wrapper-root location is still available — set `mount.overlayPath` in `cockpit.yaml` to the legacy value if you have a reason to keep it in the repo (workspace-relative paths are resolved against the workspace root; absolute paths are honoured verbatim).
- **`mount.overlayPath` schema default → `null`.** Old configs that hard-coded `docker-compose.dev-cockpit.yml` keep working unchanged. New configs can omit the field entirely.

### Added

- **`resolveOverlayPath()`** (in `mount/overlay-path.ts`) — pure helper, exported. Used by `dev-cockpit mount`'s `resolveContext` and available to profile-bundled commands that build their own overlay placement (e.g. awc-cockpit's `select` pipeline picks it up automatically through dev-cockpit's writers).

### Migration

Existing wrappers may have a leftover `<workspace>/docker-compose.dev-cockpit.yml` from older mount sessions. It's an orphan now — safe to `rm` (cockpit will write to the new location next time you `mount`). The old manifest's stored `overlayPath` field continues to be used by `mount clear`, so cleanup of in-flight sessions across the upgrade boundary just works.

## [0.2.2] — 2026-05-12

External `cockpit.yaml` — the config can now live anywhere, with a per-machine registry mapping workspaces to their YAML file.

### Added

- **`cockpit.yaml` discovery chain.** Resolves in this order: `--config <path>` CLI flag → `DEV_COCKPIT_CONFIG` env var → `<cwd>/cockpit.yaml` → `$XDG_DATA_HOME/dev-cockpit/manifest.json` lookup keyed by canonical `cwd` → friendly error pointing at `dev-cockpit link`. Lets the config live outside the workspace without losing the "just run from the workspace root" UX.
- **`dev-cockpit link` subcommand tree.** `link <config-path>` registers the current directory in the manifest; `link list` prints all mappings sorted by workspace; `link remove` unregisters the current directory. Atomic write (`.tmp` + rename), symlink-canonical keys (so `~/work` and `/Volumes/.../work` hash to the same entry).
- **`init-config -o, --output <path>`.** Write the starter config anywhere, not just `<cwd>/cockpit.yaml`. Honours the existing `cwd` parameter as the anchor when `--output` is relative.
- **`DEV_COCKPIT_CONFIG` env var** picked up by all commands as the second-precedence fallback.
- **`src/core/manifest.ts`** — read / lookup / atomic write of the project→config map. Defensive: returns `null` for missing / malformed / wrong-version files (treated as "not registered yet" rather than an error).
- **`src/core/config-discovery.ts`** — pure resolver returning `{ configPath, workspaceRoot, source }`. Profile `discoverer` hook still overrides `workspaceRoot` (e.g. awc-cockpit walks up to find the wrapper); generic users get `canonical(cwd)`.

### Changed

- **`dev` / `doctor` / `mount` (+ `status`, `clear`) now derive workspace from `canonical(cwd)`**, not `path.dirname(configPath)`. So `--config /tmp/x.yaml` from inside a wrapper keeps the workspace as the wrapper — the whole point of external configs.
- **`-c, --config` no longer defaults to `'cockpit.yaml'`** at the Commander level. Default is `undefined` so the discovery chain sees the "no explicit flag" signal and walks the precedence list. Help text describes the fallback chain.

### Fixed

- **CI publish workflow.** `npm install -g npm@latest` on Node 22's bundled npm could leave global state where `Cannot find module 'promise-retry'` fires from the half-installed npm. Switched the publish workflow to Node 24, which ships npm 11.x natively (satisfies the npm Trusted Publishing >= 11.5.1 requirement without a self-upgrade).

## [0.2.1] — 2026-05-11

Post-1.0 roadmap landing: action registry, processes (renamed from watchers), repos pane redesign, profile wizardSteps, accessibility (severity glyph + NO_COLOR), narrow-terminal responsiveness, lift-mount + bundled dist, plus a wave of fit-and-finish.

### Added

- **Action registry** with `:` command palette. `ActionConfig` has `id`, `label`, `scope` (`global` | `repos` | `repos:<id>`), `key` (single char), and a programmatic `invoke` callback. Replaces the legacy Repos `r/w/l` hardcoding — the Footer derives its legend from the registry, so profile-defined actions show up automatically.
- **`Profile.wizardSteps` extension point.** Profiles inject custom steps into `init-config -i` (e.g. awc-cockpit asks for `gitlab.host` + `gitlab.topic` during the wizard).
- **`defaultPane` config field** (`repos | output | health | help`). Profile-overridable.
- **Processes (renamed from watchers).** `config.processes[]` is the new spelling; old `config.watchers[]` is auto-migrated via the new schema migration tooling on load. `CONFIG_VERSION` bumped 1 → 2.
- **`dev-cockpit migrate-config`** persists the in-memory v1 → v2 migration to disk.
- **Repos pane redesign.** Split layout: row list on the left, per-row action panel on the right. Process-kind rows with built-in `r → restart` actions wired through the action registry. Grouped list (Repos / Docker / Processes subheadings) for navigability.
- **`dev-cockpit doctor --paths`** prints state-dir paths in `KEY=PATH` form (eval-friendly).
- **`subprocess` tag lookup.** `spawnStream({ tag })` registers the handle in a `BY_TAG` map; `findSpawnedByTag(tag)` / `killSpawnedByTag(tag)` look it up. Used by per-process restart actions.
- **`spawnStream` stdin option.** Opt-in `stdin?: 'pipe' | 'ignore' | 'inherit'` on `SpawnOptions` — pass `'ignore'` for non-interactive subprocesses so any `read`/prompt in the child gets EOF instead of blocking forever on an unwritten pipe.
- **Severity glyph** at the start of every Output line — accessibility for users who run with `NO_COLOR=1`.
- **`NO_COLOR` full-strip** — every ANSI escape is filtered when the env var is set.
- **Narrow-terminal responsiveness.** `useTerminalWidth` hook; TabBar / Help / Repos collapse cleanly under ~200 cols (legend says `j/k` instead of arrows when collapsed).
- **Wizard auto-seeds `actions[]`** from `package.json` scripts during `init-config -i`.
- **Smart defaults** for health-check `id` + `label` in the wizard (derived from check type + target).
- **Container-running health predicate uses `docker ps --filter name=<value> --format {{.State}}`** — compose-aware naming, so `name: laravel.test` matches `crossroads-laravel.test-1`.
- **Engine guard.** Friendly error message when Node < 20.12.

### Changed

- **`Repos` pane label → `Targets`** (display only; internal enum stays `repos`).
- **Mount machinery lifted into dev-cockpit core.** `mountCandidatesProvider` + `mountSymlinks` + `onMountApply` / `onMountClear` hooks on `Profile`; awc-cockpit kept its own `mount` command for its richer interactive UX (composer-aware clear semantics).
- **Bundled dist.** esbuild produces a single self-contained `dist/index.js` + helpers; consumer installs no longer need to resolve dev-cockpit's full source tree.
- **OIDC trusted publishing** on the publish workflow. No more `NODE_AUTH_TOKEN` — npm verifies the GitHub OIDC token issued by the workflow's `id-token: write` permission.

### Fixed

- **`killSpawnedByTag` race.** Promise.race against the exitCode + a SIGKILL guard could return before the `BY_TAG` finalizer ran on slower CI runners. Now awaits `exitCode` unconditionally after the race so the finalizer completes before return.
- **Repos pane title duplication** — dropped the redundant pane title; TabBar already labels the tab.
- **Wizard Repos step copy** — reflects the post-redesign reality (action panel + grouped list).

## [0.1.0] — 2026-05-10

First pre-1.0 release. The public surface (`Profile` interface with `boot` lifecycle hook, `buildCli`, the four core commands `dev / doctor / init-config / mount`, the `cockpit.yaml` schema with `version: 1`) is stable enough for first consumers to depend on. Expect minor churn until the post-1.0 roadmap items land.

### Added

- **Interactive `init-config` wizard** (`-i` / `--interactive`). Seven-step onboarding with project sniffing — auto-detects compose files + services, `package.json` scripts as watcher candidates, npm/yarn/pnpm workspaces as repo entries, and surfaces sensible defaults so most prompts just need an enter. Closes by offering to run `doctor` immediately.
- **`profile.boot` lifecycle hook**. Profiles return `{cockpitHandlers, subscribeFsEvents, cleanup}` from a single async function called after config load + workspace resolution and before the cockpit renders. Replaces the older "profile registers its own dev command" override pattern.
- **`profile.reposProvider(ctx)` receives the wizard context** so profiles can drive the Repos pane from their validated `profile.<appName>` namespace.
- **`killAllSpawned()` global subprocess registry** — every `spawnStream` auto-registers + auto-deregisters; `killAllSpawned()` SIGTERMs stragglers, awaits reap, falls back to SIGKILL after 1.5s. Eliminates the "second Ctrl-C needed to quit" class of bugs.
- **Periodic poll for `docker`-trigger health checks** (default 5s, configurable via `SchedulerDeps.dockerPollIntervalMs`, set 0 to disable). `docker compose down` mid-session now flips `container-running` / `port-open` checks red within the poll interval.
- **Synthetic test fixtures** under `tests/fixtures/`: `plain-node` (no docker block; tsc watcher; http-ok + file-exists checks) and `dockerized` (alpine heartbeat + redis; container-running + port-open checks). Smoke tests via `ink-testing-library`.
- **Generic CLI commands** — `dev / doctor / init-config / mount` — with `buildCli({ profile? })` factory. Profile-first registration: `setupCli(program)` runs before core, claimed names skip their core counterpart (one mechanism, no array mutation).
- **Health framework** with five built-in check types (`container-running`, `port-open`, `http-ok`, `file-exists`, `exec-zero`), `useHealth` hook orchestrating the scheduler + transition diffing + remediation dispatch, per-item `notify` overrides.
- **Watcher subsystem** (chokidar lazy-spawn, reactive lint, transition-only build notifications).
- **Docker subsystem** (log tailer with live-check, highlight engine, stack-trace extractor, restart helper).
- **Cockpit shell** — four-pane TUI (Repos / Output / Health / Help), tab nav, modals (filter / search), live-markdown Help loader.
- **Core modules** — config loader (zod + `version: 1` schema), state paths (`xdg_state_home/<appName>/<hash>/`), pino logger, execa subprocess wrapper, OS notifier (detached `osascript` / `notify-send` so the helper doesn't keep the parent loop alive).

### Changed

- **OS notifier no longer uses `node-notifier`.** Direct `child_process.spawn` with `detached: true` + `stdio: 'ignore'` + `unref()` so notification helpers run independently of the cockpit's lifetime.
- **`DockerLogTailer.stop()` and `WatcherManager.stop()` await child SIGTERM reap** before resolving (previously sent the signal and returned immediately, leaving handles in the event loop).
- **Bin shim filters Node 22's `ExperimentalWarning: Importing JSON modules`** (cli-boxes uses `with {type: 'json'}`). Filtered at the bin level via dynamic-import-after-emitWarning-hook.

### Fixed

- Per-phase exit cleanup so the shell prompt returns immediately on `q` — no more "second Ctrl-C needed to quit".

## [0.0.0] — early lifts

Initial scaffold + the phase 2-6 lifts of the cockpit shell + watcher / docker / health subsystems into a generic, domain-neutral library. Captured here for completeness; commit history in git is the authoritative record.
