Skip to content

fix(dcs): one relay endpoint label, record only while relaying, telemetry in real units, port validated on commit - #158

Merged
mottihoresh merged 4 commits into
mainfrom
fix/chunk-5
Oct 18, 2026
Merged

mottihoresh merged 4 commits into
mainfrom
fix/chunk-5

Conversation

@mottihoresh

@mottihoresh mottihoresh commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Chunk 5 of the fix track (#135): relay, telemetry and the settings form. Closes #134.

Item PRs squash-merged into fix/chunk-5:

Tested with a recorded replay before merge (checklist in #134); a live DCS session goes on the v0.2.0 QA run. Rebased on main after chunk 4.

BEGIN_COMMIT_OVERRIDE
fix(dcs): one relay endpoint label, record only while relaying, waiting wording matches the mode
fix(settings): telemetry in real units from the start, host and port saved on commit with validation, psu fields follow imports
END_COMMIT_OVERRIDE

…ng wording matches the mode (#156)

Part of #134 (chunk 5): X9, X12, N2 and N3. All four live in
`dcs/relay-rules.ts` and are pushed on `relay:facts`. The renderer only
reads them.

## X9: one endpoint label
- **Today:** the Relay pill and the Overview DCS Link tile each build
their own endpoint, and the two disagree. TCP showed `host:port` on the
pill and `host:port · remote` on the tile. Unicast showed `unicast
listen` on the pill and `239.255.50.10:5010 · local` (the multicast
group) on the tile. Both used the host and port as typed in the
renderer's form.
- **After:** there is one `relayEndpoint()` in `relay-rules.ts`, built
from the settings main holds and pushed as `RelayFacts.endpoint` (a
string). The pill tooltip, the DCS Link tile and the event log's relay
rows all show it. The label per transport:
  - TCP: `host:commandPort`
  - Multicast: `239.255.50.10:5010`
- Unicast: `0.0.0.0:listenPort` (the transport binds `listenPort` on
every address)

`relay-events.ts` used to build its own copy of this label; it now calls
`relayEndpoint()`. `EndpointLabel`, `pillEndpoint`, `overviewEndpoint`
and the renderer's `endpointText` were removed. The title bar no longer
passes host and port to the pill.

## X12: Record only while relaying
- **Today:** Settings › Connection's Record is enabled even while the
relay is stopped. The Connection tab's Record card is enabled only while
relaying.
- **After:** both read the same `RelaySurfaces.canRecord` boolean, from
`canRecord(relaying, recording)`. A recording can start only while
relaying. A recording that is already running can always be stopped, so
stopping the relay mid-recording never leaves a recording you can't
stop. Main also enforces the rule: `DcsRelay.mayRecord()` is checked
first in `capture:toggle`, so a refused Start never opens the save
dialog. The save dialog still opens when a recording starts.

## N2: no device-waiting wording in Monitor
- **Today:** the Overview Link tile says "relay waiting — resumes when
it connects" whenever a relay runs with the link down, Monitor included.
- **After:** "waiting" follows the same rule as the pill and the note
under Start: it shows only while the relay waits for the board. Monitor
and an undriven Replay show a new `running` value, worded "stream
running".

## N3: a driven Replay shows the waiting reason
- **Today:** "waiting for the Gateway" shows in Bridge only.
- **After:** `waitingForDevice` uses `exportToDevice` (Bridge, or Replay
with Drive Gateway on). So a driven Replay that loses the board now
shows the waiting reason: the pill tooltip says it waits for the Gateway
and turns gold, the note under Start says the same, and the Overview
says "relay waiting". `startBlocked` uses the same `exportToDevice`
rule.

## Tests changed on purpose
- `relay-rules.test.ts`: the X9, X12, N2 and N3 blocks that pinned the
old behaviour were rewritten: the per-surface endpoints, the per-surface
`canRecord`, "waiting in every mode", and "a driven Replay is never
waiting". The facts test now expects a driven Replay running with no
board to show `waiting-device`.
- `pushed-rules.seam.test.ts`: the `config:edit` facts test now expects
the Unicast endpoint `0.0.0.0:5010`. New seam tests cover X9 (all three
transports from the settings main holds), X12 (refused while stopped
with no dialog, allowed while relaying, stoppable after Stop), N2
(Monitor with no Gateway is `running`) and N3 (a driven Replay that
loses the board waits for it).
- `link-view.test.ts`: the relay inputs carry `replayDriveSerial`, and
`relayWaiting` gains a driven-Replay case.
- `t27`: the DCS Link tile expectations are now the shared label,
without the ` · remote` / ` · local` suffix, and Unicast shows its
listen port. The old "reconnecting while relaying" Link case is now
Bridge, since Monitor no longer waits. New cases: Monitor with no board
reads "stream running"; a driven Replay reads "relay waiting"; the pill
and the DCS Link tile show the same endpoint in `<App />` for TCP,
Multicast and Unicast.
- `t31`: Settings › Connection's Record is disabled while stopped and
enabled once relaying. This was contract B62; the comment noting the old
behaviour was removed.
- `t15` / `t28`: new tests. A driven Replay with no board shows the
waiting reason on the pill and in the note under Start. An undriven
Replay just plays. Monitor with no board has no waiting wording.

With the production files reverted to the base branch, 25 of these tests
fail. Both the main and renderer tests catch every item.

## How verified
`npm run lint`, `npm run lint:deps`, `npm run typecheck` and `npm test`
all pass (1414 tests). The API guard header records the type change: no
channel, intent or method was renamed. Still to do: the on-device
checklist in #134.
…saved on commit with validation, psu fields follow imports (#157)

Part of chunk 5 (#134): items X10, N5 and N8. Base: `fix/chunk-5`.

## X10: one telemetry spec
- **Today:** the idle placeholders are their own spec. IAS and Press Alt
read `% FS` until the first telemetry tick, then switch to kt and ft.
Press Alt's idle id was `D_ALT_NEEDLE`, not `ALT_FT`.
- **After:** there is one spec, `TELEMETRY` in
`main/dcs/sim-telemetry-decoder.ts`. `idleTelemetry()` returns the
decoder's live readouts with nothing decoded yet, so the gauges show kt,
ft and % from the start. The contract's `TELEMETRY_IDLE`
(`shared/dcs/ipc.ts`) is the renderer's starting value. A test pins it
to the decoder's output.
- **Side effect:** the idle Press Alt ring now uses the secondary tone,
the same as the live Press Alt ring. Before, the idle ring was primary
and only switched to secondary on the first tick. This follows from
having one spec.

## N5: host and port
- **Today:** the host and port are saved on every keystroke, and a port
that isn't a number silently saves as 7778.
- **After:** typing saves nothing. The field is saved on commit (blur or
Enter). Main checks the port in `settings-form.ts` `commandPortOf`,
which accepts whole digits from 1 to 65535. A bad port saves nothing and
pushes nothing. `config:edit` then answers `{ ok: false, error }` (new
`ConfigEditResult`). Both the Settings › Connection form and the
Connection tab's DCS Link form show main's error using the existing
`input--bad` / `aria-invalid` outline and `set-err` text. No new CSS. A
blur that follows an Enter with nothing new typed sends nothing. An
import resets the typed text and the error.
- **API guard:** `editConfig` now answers `ConfigEditResult` instead of
`AppConfig`. The frozen copy is updated on purpose, with a header note.
No channel was added, removed or renamed.

## N8: PSU limit fields
- **Today:** Settings › Cockpit's custom-limit fields hold their own
typed text, which is never refreshed after an import replaces the
settings.
- **After:** an import (and `config:get`) bumps the config store's
`formsReset`. `PowerSupply` is keyed on it, so the fields restart from
the pushed config. This works the same way as the other form fields,
which restart from the config on an import. A form's own edit still
leaves its typed text alone.

## Tests changed on purpose (they pinned the old behaviour)
- `sim-telemetry-decoder.test.ts`: the "idle readouts are their own
spec" test becomes "one spec": idle equals the live snapshot, with units
`% RPM / kt / % DN / ft / % QTY`. A new test checks that the first frame
keeps the idle ids, labels and units.
- `t27`: the idle gauges read `—kt` / `—ft` instead of `—% FS`.
- `settings-edit.test.ts`: removed the "junk port saves as 7778"
assertions. Added tests for `commandPortOf`, for a junk port being
refused with its error, and for `SettingsService.edit` saving and
pushing nothing on a junk port. Updated the tests for the new
`SavedEdit` / `ConfigEditResult` shapes.
- `pushed-rules.seam.test.ts`: `config:edit { port: '77a' }` is refused
with the error. `config.json` and `config:get` keep the last good port,
and no `config:changed` is pushed.
- `t28` / `t31`: "saved as typed" becomes "saved on commit". These tests
check that typing saves nothing, that blur and Enter each save, and that
a blur after Enter does not send again. New tests check that a junk port
shows the error with `aria-invalid` and saves nothing, and that a good
port then saves and clears the error.
- `t29` (new): renders the Cockpit and Configuration sections together.
A typed (refused) limit is followed by an import with other limits, and
the fields show the imported limits.
- `skyhawk-mock.ts`: `editConfig` answers `ConfigEditResult` the way
main does.

All new and changed tests fail on the old source. I checked this by
stashing the non-test changes: 16 tests failed.

## How verified
`npm run lint`, `npm run lint:deps`, `npm run typecheck` and `npm test`
(1421 tests) all pass locally, after rebasing onto `fix/chunk-5` at
8c48345 (#156). I have not tested on the DCS computer yet; that belongs
in the chunk 5 device session.
# Conflicts:
#	src/shared/app/ipc.compat.test-d.ts
@mottihoresh
mottihoresh marked this pull request as ready for review October 18, 2026 02:09
@mottihoresh
mottihoresh merged commit a23d145 into main Oct 18, 2026
4 checks passed
@mottihoresh
mottihoresh deleted the fix/chunk-5 branch October 18, 2026 02:10
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.

fix: chunk 5 — relay, telemetry and the settings form

1 participant