# Work Plan — uopi ## Phases Overview | Phase | Name | Focus | Milestone | | ----- | --------------------- | ---------------------------------------------------------- | ----------------------------------------- | | 0 | Scaffold | Repo structure, build toolchain, CI | Empty binary serves embedded index page | | 1 | Core Backend | Broker, WebSocket protocol, REST skeleton | Live signal fan-out working end-to-end | | 2 | EPICS Data Source | CA connect, monitor, metadata, write | Real EPICS PVs visible in browser console | | 3 | Synthetic Data Source | DSP engine, Lua sandbox, signal composition | Synthetic signals computed and streamed | | 4 | Frontend Foundation | Svelte skeleton, WS client, signal stores, view mode shell | Subscribed values rendered in browser | | 5 | View Mode Widgets | All widget types rendered in view mode | Full interactive HMI panel | | 6 | Edit Mode | Konva canvas, drag-and-drop, resize, properties pane | Can build and save a panel | | 7 | Edit Mode — Advanced | Multi-select, align/distribute, undo/redo | Complete editor UX | | 8 | Historical Data | Archive integration, time navigation UI | Replay past data in widgets | | 9 | Signal Discovery | Signal tree, CSV import, EPICS Channel Finder | Usable without manual PV entry | | 10 | Hardening | Docs, packaging, integration tests, performance | Production-ready binary | --- ## Phase 0 — Scaffold **Goal:** working build pipeline, empty binary that serves a placeholder page. - [ ] Initialise Go module (`go mod init github.com/org/uopi`) - [ ] Create directory layout: `cmd/uopi/`, `internal/`, `web/` - [ ] Svelte + Vite + TypeScript project in `web/` - [ ] `//go:embed web/dist` in backend; `Makefile` builds frontend then backend - [ ] HTTP server on configurable port; serves embedded frontend - [ ] TOML config loading (`BurntSushi/toml` or `pelletier/go-toml`) - [ ] GitHub Actions CI: `make test` on push - [ ] `README.md` with quick-start instructions **Done when:** `./dist/uopi` starts and serves an "under construction" page. --- ## Phase 1 — Core Backend **Goal:** signal broker and WebSocket protocol working with a stub data source. - [ ] Define `DataSource` interface (`internal/datasource/iface.go`) - [ ] Implement in-memory stub data source (sine wave emitter) for development - [ ] Implement `Broker` (`internal/broker/`): - Subscribe / unsubscribe with reference counting - Fan-out goroutine per signal - Clean teardown when last subscriber leaves - [ ] WebSocket handler (`internal/server/ws.go`): - `subscribe` / `unsubscribe` / `write` / `history` messages - Pushes `update` and `meta` messages to client - Graceful close on disconnect - [ ] REST API skeleton (`internal/api/`): datasources, signals, interfaces endpoints (stub responses) - [ ] Unit tests: broker fan-out, subscribe/unsubscribe lifecycle **Done when:** a `wscat` client can subscribe to the stub source and receive timed updates. --- ## Phase 2 — EPICS Data Source **Goal:** real EPICS PVs readable and writable through the broker. - [ ] CGo wrapper for EPICS `libca` (`internal/datasource/epics/`): - Context init and cleanup - `ca_create_channel` with connection callback - `ca_add_event` monitor with value callback - `ca_put` for writes - DBR_CTRL get for metadata (units, limits, enum strings) - [ ] Map CA DBR types to internal `DataType` enum - [ ] Implement `DataSource` interface for EPICS - [ ] Config: `ca_addr_list`, reconnect interval - [ ] Handle disconnected/reconnected channels gracefully (quality = Bad/Uncertain) - [ ] Integration test with SoftIOC (gated on `EPICS_BASE` env var) **Done when:** a PV subscription round-trips from IOC → broker → WebSocket client with correct timestamp and units. --- ## Phase 3 — Synthetic Data Source **Goal:** users can define computed signals from existing signals. - [ ] Define synthetic signal definition schema (JSON) - [ ] Implement DAG evaluator: nodes re-evaluated on upstream change - [ ] Built-in node types (`internal/dsp/`): - Arithmetic: gain, offset, add, subtract, multiply, divide - Statistics: moving average (by count, by time), RMS - Filters: IIR lowpass/highpass/bandpass (via gonum or biquad) - Calculus: finite-difference derivative, cumulative integral - FFT / inverse FFT (gonum/dft) - Threshold / clamp - Custom expression (simple formula parser) - [ ] Lua node: sandboxed `gopher-lua` state per signal; inputs as globals - [ ] Load synthetic definitions from `synthetic.json` at startup - [ ] REST endpoint to CRUD synthetic signal definitions (persists to JSON file) - [ ] Unit tests for each DSP node type **Done when:** a synthetic moving-average of an EPICS PV is visible in the WebSocket stream. --- ## Phase 4 — Frontend Foundation **Goal:** Svelte app connects to WebSocket and reactively displays values. - [ ] Svelte project structure: routes for view (`/`) and edit (`/edit`) - [ ] WebSocket client (`ws.ts`): connect, reconnect, message dispatch - [ ] Signal store factory (`stores.ts`): `getStore(signalName)` returns reactive store - [ ] Subscription manager: reference counting mirrors broker's - [ ] View mode shell: collapsible interface list pane + empty canvas area - [ ] Fetch and list interfaces from REST API - [ ] Load interface XML; parse widget definitions - [ ] Render a minimal text-view widget reactively from signal store - [ ] Basic responsive layout; DPI adaptation for canvas **Done when:** loading a manually crafted XML interface displays live PV values. --- ## Phase 5 — View Mode Widgets **Goal:** all widget types rendered and interactive in view mode. - [ ] Text view widget - [ ] Gauge widget (SVG arc, configurable range/thresholds) - [ ] Vertical / horizontal bar widget - [ ] LED widget (condition evaluator, configurable colours) - [ ] Multi-LED widget (bitset, per-bit labels) - [ ] Set-value widget (input + Set button; sends `write` over WS) - [ ] Button widget (sends fixed value on click) - [ ] Plot widget: - Time-series (uPlot, streaming buffer of N points) - FFT, waterfall, histogram, bar chart, logic analyser (ECharts) - Multi-signal support; legend - [ ] Text label (static) - [ ] Image widget (base64 or server URL) - [ ] Link widget (navigates to another interface) - [ ] Right-click context menu: signal info dialog, copy name, export CSV - [ ] Svelte component tests for each widget type (mock store) **Done when:** a complete HMI panel with multiple widget types runs smoothly at 60 fps. --- ## Phase 6 — Edit Mode (Core) **Goal:** users can build and save an interface from scratch. - [ ] Konva stage setup in edit mode; `devicePixelRatio` scaling - [ ] Widget renderer adapter: same widget components rendered on Konva layer - [ ] Signal tree pane: fetch signal list, tree display, filter/search - [ ] Drag signal from tree → drop on canvas → widget type picker - [ ] Place widget at drop coordinates with default size - [ ] Single selection: bounding box + resize handles via Konva `Transformer` - [ ] Move widget by dragging body - [ ] Delete widget (× button or Del key) - [ ] Properties pane: common options (label, position, size) - [ ] Per-widget property editors (range, colour, plot type, etc.) - [ ] Save interface to server (POST/PUT XML) - [ ] Load interface from server (GET XML, populate canvas) - [ ] Export / import local XML file **Done when:** an engineer can create a panel with 5+ widgets, save it, reload it, and see live data. --- ## Phase 7 — Edit Mode (Advanced) **Goal:** complete editing UX matching the functional spec. - [ ] Multi-select: Ctrl+click toggle; rubber-band area select - [ ] Group move: drag any selected widget to move all - [ ] Group delete: Del key on selection - [ ] Align toolbar: left / center-H / right / top / center-V / bottom - [ ] Distribute toolbar: evenly by center / by gap (H and V) - [ ] Undo / redo: command pattern, Ctrl+Z / Ctrl+Shift+Z - [ ] Snap-to-grid (optional, toggle in toolbar) - [ ] Add text label tool - [ ] Add image tool (upload to server or embed base64) - [ ] Add link tool - [ ] Collapsible signal tree pane and properties pane (toggle buttons) - [ ] Right-click on interface in list: Edit / Clone / Delete **Done when:** the editor feels complete and the undo stack works reliably. --- ## Phase 8 — Historical Data **Goal:** users can navigate to past timestamps using archive data. - [ ] EPICS Archive Appliance HTTP API client (`internal/datasource/epics/archive.go`) - [ ] Broker `History()` dispatch: route to correct data source's `History()` impl - [ ] WebSocket `history` request / response handling - [ ] Frontend: time range picker in view mode toolbar - [ ] "Live" button: flushes history state, re-subscribes to live updates - [ ] Plot widget: switch between streaming and historical range mode - [ ] Point-value widgets: show value at selected timestamp **Done when:** a plot can display 24 hours of archived data with a time slider. --- ## Phase 9 — Signal Discovery **Goal:** users can find signals without knowing their names in advance. - [ ] EPICS: attempt Channel Finder or `cainfo` enumeration; fall back to manual entry - [ ] Signal tree: lazy-load children on expand - [ ] Manual add custom PV (EPICS): text input in signal tree - [ ] New synthetic signal wizard: name, inputs, node graph UI - [ ] CSV import: parse `NAME, DataSource, DS_PARAMETERS`; add to tree **Done when:** a new user can find and subscribe to a PV without prior knowledge. --- ## Phase 10 — Hardening **Goal:** production-quality binary, docs, performance validation. - [ ] End-to-end integration tests (real SoftIOC, headless browser via Playwright) - [ ] Benchmark: 20 clients × 100 signals; verify < 5 ms fan-out latency - [ ] Benchmark: frontend at 10 Hz update rate; verify 60 fps - [ ] Static binary validation: run on RHEL 7 (Docker image `centos:7`) - [ ] Security: Lua sandbox audit; XML XXE protection; write-permission guard - [ ] Graceful shutdown: drain WS connections, flush pending writes - [ ] Structured logging (`log/slog`) - [ ] `/metrics` endpoint (Prometheus format) for server monitoring - [ ] Complete `README.md` and operator docs - [ ] Release: `goreleaser` config for Linux amd64 + arm64 binaries **Done when:** binary passes integration tests on CentOS 7 container with a SoftIOC. --- ## Estimated Effort These are rough single-developer estimates. Parallel work across backend and frontend where possible will reduce calendar time. | Phase | Effort | | --------- | ---------------- | | 0 | 1–2 days | | 1 | 3–5 days | | 2 | 1–2 weeks | | 3 | 1–2 weeks | | 4 | 3–5 days | | 5 | 2–3 weeks | | 6 | 2–3 weeks | | 7 | 1–2 weeks | | 8 | 1 week | | 9 | 1 week | | 10 | 1–2 weeks | | **Total** | **~14–20 weeks** |