# Cross-Plan Follow-ups Items surfaced during review of one plan that belong to a later plan. Prevents silent drops. ## Deferred to Plan 7 (CI + observability + hardening) Originally scoped as Plan 5, then re-deferred to Plan 6 when Plan 5 became the N8N Node Editor Dialog and Plan 6 became Pinned Node Data. These items now go to Plan 7. - **CI pipeline.** There is no automated CI yet. Add a workflow (Gitea Actions if the repo is hosted on Gitea, otherwise GitHub Actions) that runs `dotnet build`, `dotnet test`, `pnpm --dir frontend test`, `pnpm --dir frontend build`, and the Playwright smoke on push to `main`. The Playwright job needs `pnpm exec playwright install --with-deps chromium` before `pnpm --dir frontend test:e2e`. Bonus: build + push the Docker image on tag pushes. - **Persist ASP.NET Core DataProtection keys under `/data`.** The Plan-4 Dockerfile smoke run showed the default DataProtection key ring is written to `/root/.aspnet/DataProtection-Keys` inside the container — ephemeral. On container replacement (redeploy, OOM restart, host reboot) every issued cookie is invalidated and every logged-in user is silently kicked out. Fix in Plan 7: wire `AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(settings.DataDirectory, "dp-keys")))` in `Program.cs` and cover with an xUnit test that verifies the directory is created on first boot. ## Deferred from Plan 6 (Pinned Node Data) - **Skip upstream execution when downstream has a pin.** Currently upstream ancestors of a pinned node still run and their output is discarded. Would need dependency analysis + a "no successors need my output" pass. - **Pin both input AND output on the same action node.** Action nodes only expose the input editor; output-pin on action nodes would let operators "canned-response" a node during test runs. - **Multi-port pinning.** Only port 0 today. - **Pin sharing / templates.** Copy-pin-between-nodes UI. - **Diff indicator in the dialog:** show "what this node WOULD have received" vs. "what the pin says it will receive". - **Backend index/column for querying "workflows with pinned data."** Currently a stale pin on a live workflow can only be found by opening it in the editor. ## Deferred from Plan 5 (Node Editor Dialog) - **Webhook stand-by mode.** Trigger option where an active workflow pauses on the webhook node and only continues when a matching HTTP request arrives, instead of running through the first request. Needs pause/resume semantics in `Executor`; separate spec. - **Drag-and-drop insertion** from `JsonTree` to `ExpressionField`. Currently only click-to-insert at caret is supported. - **Multi-color / semantic highlighting** inside `{…}` (property vs bracket vs literal). Single amber color ships in Plan 5. - **Cross-node expressions** like `{nodes..output.body}` and `{nodes..output[0].json.body}`. Requires the executor to expose a scoped-view of run state to `TemplateEngine`. - **JsonTree accessibility.** Add `role="tree"`/`role="treeitem"` + `aria-expanded`/`aria-level` on branch buttons so screen readers can navigate the structure. MVP ships without these; usable for sighted operators only. - **`msw` module alias cleanup.** `frontend/src/testing/msw.ts` exports both `server` (new) and `mswServer` (legacy). Four existing test files still use `mswServer`. Migrate them and drop the alias so there's one canonical name. - **`WebhookUrlHint` setTimeout leak on unmount.** The 1500ms `setCopied(false)` fires even if the dialog closes mid-window. Harmless in React 18 but a lingering timer. Guard with a ref or `useEffect` cleanup. - **`useMemo` around `normalizePorts` in `NodeEditorDialog`.** Runs every render, allocating fresh `Port[]` arrays; blocks downstream `React.memo` on `PortsColumn`. Cheap-per-run but wasteful. - **Tab-wrap focus-trap test.** Implementation is in place; jsdom doesn't simulate real browser tab-order so it's not unit-tested. Add a Playwright smoke assertion if the behavior ever regresses in practice. - **Preview numeric-precision drift.** `frontend/src/expr/templateEngine.ts` renders JS numbers via `String(v)`, which loses fidelity vs the backend's `GetRawText()` for over-precision or non-normalized JSON (e.g., `1.50`, `9007199254740993`). Documented in the header comment; would need JSON source-text preservation to fix.