From 726630563098ea62b0e4e66218c7cf13c41554ba Mon Sep 17 00:00:00 2001 From: Karstein Phobic Nyvold Kvistad Date: Wed, 29 Apr 2026 09:07:22 +0200 Subject: [PATCH] plan: phobiCS-tui v0.3 inline live values Scopes the v0.3 work as 8 TDD-able tasks with a one-way live-values pump (server writes tui-live-values.json next to tui-state.json; TUI Viewer reads it on a 500ms poll and overlays values inline next to declared variable names). The original blocker (connect_to_device script bug) is gone: the SP21+ fork now probes both LoginMode and OnlineChangeOption enums across SPs and tries multiple call shapes (see src/scripts/connect_to_device.py). Memory updated to reflect the fix. No code changes here -- pure planning doc, ready to be picked up as a separate PR. --- ...2026-04-29-phobics-tui-v0.3-live-values.md | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-29-phobics-tui-v0.3-live-values.md diff --git a/docs/superpowers/plans/2026-04-29-phobics-tui-v0.3-live-values.md b/docs/superpowers/plans/2026-04-29-phobics-tui-v0.3-live-values.md new file mode 100644 index 0000000..2183229 --- /dev/null +++ b/docs/superpowers/plans/2026-04-29-phobics-tui-v0.3-live-values.md @@ -0,0 +1,116 @@ +# phobiCS-tui v0.3 — inline live values + +Status: **scoped, ready to implement.** The original blocker (`connect_to_device` script bug) was fixed in the SP21+ fork; see `src/scripts/connect_to_device.py:30-93`. + +## Goal + +When the user is browsing a POU in `phobiCS-tui` and the runtime is online, overlay each declared variable's live value inline next to its declaration in the Viewer: + +``` + 3 counter : INT := 0; ◀ live: 47 + 4 bRunning : BOOL; ◀ live: TRUE +``` + +Updates every 500 ms while the runtime is online. Off when no runtime, no overlay. + +## Architecture (one-way pump) + +The TUI is the user-facing process; the MCP server is the agent-facing process. They already communicate one-way through `tui-state.json` (TUI writes, server reads via `get_user_selection`). + +This adds the **other** direction: + +- **Server** writes a `tui-live-values.json` snapshot next to `tui-state.json`. +- **TUI** Viewer reads it on a 500 ms timer and overlays values for the currently-displayed POU. + +The server pump only runs when: +1. `--live-values` CLI flag is set on the server, AND +2. `tui-state.json` exists and is fresh (60 s window, same as `get_user_selection`), AND +3. The runtime for that POU's project is online. + +## State-file shape + +`tui-live-values.json` (atomic write, same `.tmp` + rename pattern as `tui-state.json`): + +```json +{ + "version": 1, + "updated_at": "2026-04-29T08:14:55.123Z", + "project_dir": "/abs/project/dir", + "device": "CodesysRpi", + "pou_name": "PLC_PRG", + "values": { + "counter": { "value": "47", "type": "INT", "ts": 1745916895100 }, + "bRunning": { "value": "TRUE", "type": "BOOL", "ts": 1745916895100 } + } +} +``` + +`values` is keyed by the bare variable name (no `PLC_PRG.` prefix). The TUI matches by exact string against tokens emitted by the highlighter. + +## Tasks + +### Task 1: shared types + path helper + +- Create `src/tui/shared/live-values.ts`: `LiveValuesPayload` interface. +- Add `liveValuesFilePath()` to `src/tui/shared/state-paths.ts` — same dir as `stateFilePath()`, filename `tui-live-values.json`. +- Tests: 4 cases mirroring the `state-paths` tests. + +### Task 2: TUI atomic reader + +- Create `src/tui/shared/live-values-read.ts`: `readLiveValues(filePath)` returns `{ status: 'ok' | 'missing' | 'stale' | 'invalid' }` with same `FRESHNESS_MS = 5_000` (tighter than the selection file — values stop being interesting fast). +- Tests: 4 cases (fresh / missing / stale / malformed). + +### Task 3: Viewer overlay + +- Add `liveValues?: Record` prop to ``. +- Walk the highlighted token rows; if a row contains a `text` token whose trimmed text is a key in `liveValues`, append ` ◀ live: ` (in green) at end of row. +- Tests: 3 cases — no liveValues prop = no overlay; with prop = overlay appears for matching var; non-matching vars unchanged. + +### Task 4: TUI live-values hook + Browser wiring + +- `useLiveValues(filePath, pouName)` hook in `src/tui/browser/useLiveValues.ts`: polls every 500 ms, returns `Record | null`. Returns `null` when payload is missing/stale or `pou_name` doesn't match the requested `pouName`. +- Browser passes `liveValues={...}` to `` when cursor is on a POU. +- Tests: 2 cases — match returns map; mismatch returns null. + +### Task 5: server-side reader for live-values write + +- Create `src/live-values-write.ts`: `writeLiveValues(filePath, payload)` mirrors `state-write.ts` (atomic `.tmp` + rename). +- Tests: 3 cases (envelope, parent dir auto-create, no .tmp residue). + +### Task 6: server-side pump + +- Create `src/live-values-pump.ts`: + - `class LiveValuesPump` — owns a `setInterval` (default 500 ms). + - On each tick: read `tui-state.json` via existing `readSelection`. If status != ok or selection is older than freshness, skip. + - Read the POU's declaration from the mirror (already exposed via `mcp-mirror//.../.st`). + - Parse the `VAR ... END_VAR` block to get var names. Use the existing parsing helpers if any; otherwise a minimal regex `\b([A-Za-z_]\w*)\s*:` is fine (skip lines starting with `(*` or `//`). + - For each var, call into the existing `read_variable` script via `executor.executeScript()`. Collect `{ name -> value }`. + - `writeLiveValues(...)` the payload. + - Errors are silent (debug-log only) — pump must never crash. +- `start()` / `stop()` lifecycle. +- Tests: parse VAR block from canned input; pump tick writes file with correct structure (mock executor + fs). + +### Task 7: server-side wiring + +- `ServerConfig.liveValues?: boolean` in `src/types.ts`. +- `bin.ts`: `--live-values` flag, plumb into config, banner `Live values: ENABLED (poll 500ms)`. +- In `startMcpServer`: if `config.liveValues`, instantiate `LiveValuesPump` and `start()` it; `stop()` in shutdown handler. +- Tests: pump starts/stops in a unit test using a fake interval. + +### Task 8: README + commit + +- Document `--live-values` and the inline-value display in the `## phobiCS-tui` section. +- Commit + open PR. + +## Out of scope for v0.3 + +- Sub-property values (`PLC_PRG.fbX.bSomething`) — only top-level vars on the displayed POU. +- Write-from-TUI (changing values from the keyboard). Stays in `write_variable` MCP tool. +- Custom poll interval CLI flag — fixed at 500 ms in v0.3. +- ARRAY / STRUCT pretty-printing — show whatever `read_variable` returns as a string. + +## Risk / known unknowns + +- `read_variable` per var is N round-trips per tick. 500 ms × 10 vars = 50 ms/var headroom. Existing `read_variable` script might be slow enough to make this infeasible for large POUs. If so: switch to a single-script-per-tick that reads N vars in one CODESYS call. +- The CODESYS UI may pop a credential dialog mid-pump. Pump must catch and back off (longer interval after first failure, e.g. 5 s). +- TUI 500 ms file poll on Windows may cause noticeable disk I/O. Mitigate: only poll while `pou_name` is set (i.e., user is on a POU row).