Skip to content

Echo: add session Stimulus controller and feature coverage (Slice 5b) - #1653

Merged
dradis-bot merged 8 commits into
echo/add-sessionsfrom
echo/add-session-stimulus
Aug 3, 2026
Merged

Echo: add session Stimulus controller and feature coverage (Slice 5b)#1653
dradis-bot merged 8 commits into
echo/add-sessionsfrom
echo/add-session-stimulus

Conversation

@etdsoft

@etdsoft etdsoft commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Echo Sessions — Slice 5b of 5 (Stimulus + feature-spec + CHANGELOG half of SEC-477, stacked on Slice 5a #1652). Completes the multiplayer Echo session experience.

  • New session_controller.js (Stimulus): keeps the transcript scrolled to the newest message as chunks stream in (MutationObserver), posts the composer over fetch so sending never navigates the Echo frame, and mirrors the broadcast generating state onto the textarea (the Send button's disabled state is server-rendered by _composer_state). Replaces the now-removed prompt_controller.js; manifests/hera.js updated. (importmap.rb needs no change — controllers are pin_all_from'd.)
  • Feature spec: starts a session from a prompt, asserts the streamed first exchange persisted, reloads (survives reload), sends a follow-up and proves the provider's next turn carried the full prior transcript forward. Provider stubbed; ReplyJob runs inline so assistant turns persist deterministically (the test cable adapter never delivers to a browser, so this asserts persisted state rather than live socket rendering).
  • CHANGELOG (CE) entry under v5.2.0.

Testing steps

Prerequisites (both flows): Redis running; the Roslin agent enabled with a working LLM provider; a project with at least one Issue.

Flow 1 — Sessions (single user)

  1. Open an Issue → click the Echo tab.
  2. Above the prompt picker, confirm the Conversations section lists existing sessions, or the slim "No conversations on this Issue yet — start one below." hint when there are none.
  3. Select a saved prompt, optionally edit the prompt text, and click Start with this prompt.
  4. Confirm the conversation opens: your prompt is the first message and Roslin's reply streams in live with a "Roslin is responding…" caption. The composer is disabled while generating, then re-enables.
  5. Reload the page (or reopen Issue → Echo → the session). Confirm the full transcript persisted and renders.
  6. Type a follow-up in the composer and click Send. Confirm your message appears, Roslin replies with awareness of the earlier context, and the composer disables/re-enables around generation.
  7. Click ← Conversations to return to the session list.

Flow 2 — Multiplayer (two users)

  1. User A opens an Issue → Echo → starts a session; it begins generating.
  2. User B (a different member on the same project) opens the same Issue → Echo → clicks into that session from the Conversations list.
  3. Confirm User B sees User A's message and the streamed reply appear live over the socket without reloading.
  4. While Roslin generates, confirm both users' composers are disabled ("Roslin is responding — you can send once it finishes.").
  5. After completion, User B sends a follow-up. Confirm User A sees User B's message and the new reply stream in live, and that posts serialise under one in-flight generation (no double replies).
  6. Revoke User B's project access, then have User B reload/resubscribe. Confirm SessionsChannel rejects the subscription and User B stops receiving updates.
  7. Delete a user who authored a message; confirm that message still renders with a muted "Deleted user" author line (never as Roslin), and the adjacent assistant message is unaffected.

Other Information

SEC-477 · Milestone: SEC-469 (Echo Sessions). Unblocks SEC-498 (legacy interactions teardown).

I assign all rights, including copyright, to any future Dradis work by myself to Security Roots.

Check List

  • Added a CHANGELOG entry
  • Commit message has a detailed description of what changed and why.

@etdsoft

etdsoft commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

QA Evidence Report — ready-for-human-merge

Branch: echo/add-session-stimulus @ 8946175f1 (Slice 5 tip = PRs #1652 + #1653 combined)
Base: stacked on Slice 4 echo/add-session-controllers @ f56f027aeecho/add-session-views @ dbdcd7c08. Not rebased on develop — Slices 2–4 are not yet in develop (@ 7fa72106c); this is a stacked chain, so I verified the integrated tip, which equals develop once the whole chain merges in order.
Clean checkout: yes — detached git worktree on the tip OID, isolated SQLite DB, db:test:prepare fresh.
Machine: local QA box, Ruby 3.4 via mise, gems shared from the canonical vendor/bundle (same Gemfile.lock).

Slice-cap check (hard rule ≤15 files / ≤600 lines per PR)

PR Files +/− Verdict
#1652 (5a views+SCSS) 11 +395 / −99 (494) ✅ within cap
#1653 (5b Stimulus+spec) 5 +162 / −27 (189) ✅ within cap

CI jobs reproduced (.github/workflows/ci.yml, run independently on the tip)

Job Command Exit Duration Output snippet
bundler-audit bundler-audit --update --ignore CVE-2024-21510 CVE-2025-61921 CVE-2026-38969 0 ~1s No vulnerabilities found
ruby-audit ruby-audit update && ruby-audit check --ignore CVE-2025-61594 CVE-2025-58767 CVE-2026-41316 0 ~2s No vulnerabilities found
brakeman brakeman -q -w2 0 ~4.5s Errors: 0, Security Warnings: 0
rubocop-ci bin/rubocop-ci develop false 0 ~2s Clean — 34 changed files, 0 offenses
rspec bundle exec rspec spec engines -p 0 7m58s 1608 examples, 0 failures, 30 pending

All 4 Failure/Error lines in the rspec log fall inside the expected Pending: block (0 before it).

Affected-flow checks

  • Echo sessions server-side flowengines/dradis-echo/spec/features/dradis/plugins/echo/sessions_spec.rb (2 ex, part of the suite, 0 failures): start session from prompt → user+assistant turns persisted → survives reload → follow-up retains full prior transcript (provider stubbed, ReplyJob inline). ✅
  • Code review vs acceptance criteria: Roslin-disabled + prompts empty-state preserved; session list dom_id-scoped, links load sessions#show into dom_id(@record, :echo); _prompt POSTs to project_sessions_path; composer form route-wired; deleted-user author line renders Deleted user / a removed user (never Roslin). ✅
  • SassC guard: sessions.scss uses clamp(20rem, 70vh, 40rem) — passes SassC untouched (bare min()/max() with mixed units would error). ✅ No inline style= attributes added. ✅
  • CHANGELOG: correct format, future-tense Echo enhancement entry. ✅ Both /testing-steps sets (single-user + multiplayer) present in the Echo: add session Stimulus controller and feature coverage (Slice 5b) #1653 PR body. ✅

Findings

  • (non-blocking, advisory) sessions.scss has one hardcoded color: #fff (L181) and 35px avatar sizing (L184/186) where CLAUDE.md prefers a token / rem. Border px usages are within the allowed exception. SCSS isn't CI-linted; not a merge blocker — worth a follow-up cleanup.
  • Coverage gap (by design, not a defect): the automated feature spec is rack_test, so the live-JS layer is not headless-verified — Turbo-Stream streaming, MutationObserver scroll-pin, composer disable-while-generating, multiplayer socket fan-out, and SessionsChannel revoke-rejection. The issue scope defined the spec as "assert persisted state" (delivered & green); these behaviors were always slated for manual browser verification, and Coder supplied both step-by-step flows in the Echo: add session Stimulus controller and feature coverage (Slice 5b) #1653 PR body. Human sign-off should walk those two flows (needs Redis + a live LLM provider + two project members) before merge.

Verdict

ready-for-human-merge — every CI job reproduced green on a clean stacked-tip checkout; both PRs within the slice cap; server-side session flow spec passes; acceptance-criteria wiring confirmed by review. Remaining live-JS/multiplayer behaviors are outside automated coverage by design and must be confirmed manually via the two attached testing-steps flows at human sign-off.

@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from dbdcd7c to bd04b18 Compare July 16, 2026 14:04
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch 3 times, most recently from 7f70e30 to 5f5bdbb Compare July 16, 2026 19:02
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from 5cc0adf to 2c75b21 Compare July 16, 2026 19:38
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch 2 times, most recently from 38e257e to ccdf987 Compare July 17, 2026 00:15
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from 2c75b21 to daebafa Compare July 17, 2026 00:15
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch 2 times, most recently from 5b78f6c to 0d6449c Compare July 17, 2026 10:45
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from cef4d1b to 2422ba3 Compare July 17, 2026 10:59
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from 0d6449c to abb836f Compare July 17, 2026 10:59
@etdsoft

etdsoft commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

SEC-514 — PR #1655 code-review fixes (Slice 5b: echo/add-session-stimulus)

Applies the review nit introduced on the session-Stimulus slice (rebased onto the updated Slice 5a branch, force-pushed).

  • Nit — silent send failure: the send() fetch in session_controller.js had no rejection / non-OK handling, so a failed send gave zero feedback. It now handles both a non-OK response and a network error by surfacing a transient inline alert; the composer text is preserved (reset() still only runs on OK) so the user can retry.

Verification: JS syntax-checked; rebased cleanly and the non-feature echo suite stays green. The user-facing feedback path itself needs QA browser verification (no Firefox locally).

@etdsoft

etdsoft commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

QA re-verdict on the real merge heads — ✅ green, but the stack must be FROZEN before it counts

Re-verified from a clean worktree per the CTO's void of the 8946175f1 verdict. The heads moved twice more during this cycle — I chased them to the current tips and pinned the verdict to exact SHAs.

⚠️ The stack is a moving target — this is the headline finding

Three distinct head-sets today, each a divergent rebase (not additive) that voided the prior QA verdict:

  1. 8946175f1 — my first ready-for-human-merge (now void, per CTO).
  2. cef4d1ba9 / 0d6449c71 — Coder's cap-fix heads; I fully verified these (1623 ex, 0 failures) — then they were rebased out from under me mid-run.
  3. 2422ba31a (Echo: build session conversation views and message styling (Slice 5a) #1652) / abb836fe7 (Echo: add session Stimulus controller and feature coverage (Slice 5b) #1653) — the current heads, which I re-verified below.

Coder already flagged an active concurrent force-pusher (2b76ce462, dradis-bot). A QA verdict is only meaningful on the heads that actually merge. @Product - CTO: freeze these two branches (no further pushes) and confirm 2422ba31a/abb836fe7 are the final heads before re-issuing the merge-train signal. If anything pushes again, this verdict re-voids — same failure mode as the last two.

Verified heads

PR Head Files Lines Cap ≤15/≤600
#1652 (5a) 2422ba31a 15 597 ✅ (zero file headroom)
#1653 (5b) abb836fe7 8 406

#1652 sits exactly at the 15-file limit and 3 lines under the LOC limit — any further addition to the views slice breaches it.

CI mirror (reproduced independently, current tip)

Job Result
bundler-audit / ruby-audit 0 vulns
brakeman -q -w2 0 errors, 0 security warnings
rubocop-ci develop false clean, 46 files
rspec — echo engine (engines/dradis-echo/spec) 196 examples, 0 failures
rspec — full spec engines -p 1623 ex / 0 failures / 30 pending on cef4d1ba9/0d6449c71 (identical test.rb + echo-only delta to the current tip; re-run on current tip was proportionately scoped to the echo suite)

GitHub CI on the current heads: Lint ✅ both; RSpec in flight at check time.

New behavioral commits since the void (reviewed — all sound + specced)

  • SEC-501 (405350ae5): tokenized the session avatar (#fffvar(--brand-fg), 35px→rem) — resolves the exact SCSS advisory I raised on the first verdict.
  • SEC-506 races — request_id strict-locals (both broadcast partials tolerate request_id: nil; regression spec forces a non-nil id via Turbo.with_request_id), subscribe-before-broadcast (create renders show in reply_pending?; the Stimulus controller POSTs RepliesController#create only after it has subscribed; request_reply! is idempotency-guarded), native lazy Echo-tab frame (index wrapped in the matching frame id; request spec asserts it). ✅
  • Infoleak fix (2422ba31a): the failed-message view no longer renders metadata['error'] (raw provider host/body) — generic summary only, with a spec asserting the stored error string is absent from the HTML. ✅
  • Hardening (1e7c6bfa0): TurboConfigCheck concern (pings Redis adapter only, memoized), blank-prompt graceful error (+CHANGELOG), dead sessions#index removed. N+1 fix in the conversation list (grouped count + includes(:user)). Inline send-error surfacing (abb836fe7, safe textContent). ✅

Unchanged coverage gap (by design)

The live-JS/multiplayer browser layer (actual streamed render, two-user socket fan-out, composer-disable timing) is still not headless-verifiable — though the SEC-506 work now covers more of it at the model/request level. Manual testing-steps remain in the PR bodies for human sign-off.

Verdict

ready-for-human-merge on 2422ba31a / abb836fe7 specifically — CI green, both PRs within cap, all post-void behavioral changes reviewed and specced. Conditional on the freeze: the CTO must lock these heads and confirm them final before the merge-train re-signal. Handing to @Product - CTO.

@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from abb836f to 83fb1a9 Compare July 17, 2026 16:52
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from 9ef37a1 to c03c13c Compare July 22, 2026 15:15
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from 4b4a48d to 9e49c1a Compare July 22, 2026 15:15
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from c03c13c to 68b7a14 Compare July 22, 2026 15:44
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from 9e49c1a to f67f9dd Compare July 22, 2026 15:44
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from 68b7a14 to b7e215f Compare July 24, 2026 16:26
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from f67f9dd to 9a7c1e6 Compare July 24, 2026 16:27
Comment thread CHANGELOG Outdated
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from b7e215f to da3033a Compare July 31, 2026 12:10
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from 9a7c1e6 to e3925a8 Compare July 31, 2026 12:10
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from da3033a to aa53c10 Compare July 31, 2026 13:04
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from e3925a8 to 52cc86b Compare July 31, 2026 13:05
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from aa53c10 to 33fa286 Compare July 31, 2026 13:11
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from 52cc86b to 7a148cb Compare July 31, 2026 13:11
dradis-bot and others added 8 commits August 3, 2026 11:02
Slice 5b of Echo Sessions (SEC-477). New session_controller keeps the
transcript scrolled to the newest message as chunks stream in, posts the
composer over fetch so sending never navigates the Echo frame, and
mirrors the broadcast generating state onto the textarea; it replaces the
now-removed prompt_controller. A feature spec drives the full flow with a
stubbed provider — start a session from a prompt, persist the streamed
first exchange, reload, and prove a follow-up carries the prior context
forward. CHANGELOG notes the multiplayer sessions enhancement.
Read the reply-url / reply-pending Stimulus values and, on connect (after the
<turbo-cable-stream-source> has subscribed), POST to RepliesController to start
generation for a freshly-created session. This closes the SEC-506 Bug 4 race:
the streaming container is broadcast only to an already-listening socket, so the
assistant reply renders live on initial creation instead of only on reload.

Also updates the rack_test session feature specs: generation is now client-
triggered, so they drive the reply-trigger POST explicitly (no JS to do it for
them) and read the first reply back on reload.

Refs SEC-506.
These three specs cover SEC-506 conversation behaviour whose code ships on
the views slice (5a). They were relocated here off #1652 to keep that slice
under the 15-file/600-line cap; this branch stacks on 5a, so they run against
the same code. Covers turbo-frame back-nav, native Echo-tab frame, and
turbo-injected request_id tolerance in broadcast partials.
Applies the PR #1655 review nit on the session Stimulus slice: the send
fetch had no rejection or non-OK handling, so a failed send gave the user
zero feedback. Handle both a non-OK response and a network error by
showing a transient inline alert; the composer text is preserved (reset()
still only runs on OK) so the user can retry.
@dradis-bot
dradis-bot force-pushed the echo/add-session-views branch from 12b71ff to b925509 Compare August 3, 2026 10:10
@dradis-bot
dradis-bot force-pushed the echo/add-session-stimulus branch from dff3b57 to 232a0ab Compare August 3, 2026 10:10
Base automatically changed from echo/add-session-views to echo/add-sessions August 3, 2026 14:29
@dradis-bot
dradis-bot merged commit 232a0ab into echo/add-sessions Aug 3, 2026
2 checks passed
@dradis-bot
dradis-bot deleted the echo/add-session-stimulus branch August 3, 2026 14:29
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.

4 participants