Skip to content

feat: incremental tile streaming — host listeners + edit/delete/reorder gates - #134

Merged
romer8 merged 3 commits into
feature/tethysdash-mcp-serverfrom
feat/incremental-tile-streaming-host
May 28, 2026
Merged

feat: incremental tile streaming — host listeners + edit/delete/reorder gates#134
romer8 merged 3 commits into
feature/tethysdash-mcp-serverfrom
feat/incremental-tile-streaming-host

Conversation

@romer8

@romer8 romer8 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 2 of docs/plans/2026-05-28-002-feat-chatbox-core-incremental-tile-streaming-plan.md. Pairs with chatbox-core PR #45 (merged + published as @aquaveo/chatbox-core@0.16.0-beta.0).

This PR wires tethysdash to the new chatbox-core events: dashboard listens for streaming lifecycle, per-tile edit/delete/reorder affordances no-op during chatbox turns to prevent racing with the LLM's patch_visualization calls.

What's in this PR

Unit 6 — StreamingContext + DashboardLoader listener

  • New export const StreamingContext = createContext(); in reactapp/components/contexts/Contexts.js.
  • DashboardLoader.js adds const [isStreaming, setIsStreaming] = useState(false) separate from disabledEditingMovement (the existing reset effect at lines 75-79 zeroes disabledEditingMovement when isEditing flips false — coupling isStreaming would have zeroed it spuriously).
  • New useEffect registers window.addEventListener("tethysdash:turn-start" / "tethysdash:turn-end") with proper cleanup on unmount.
  • New streamingContextValue memo + new <StreamingContext.Provider> wrap in the existing provider tree.
  • Dedicated context rather than extending DisabledEditingMovementContext so the 4+ other consumers of that context (Header, DashboardLayout, PopupLayoutEditor) do NOT re-render on every turn boundary.

Unit 7 — DashboardItem edit/delete/reorder gates + DashboardItemDropdown

  • DashboardItem.js reads isStreaming from StreamingContext and short-circuits the three config-mutating handlers:
    • editGridItem: guard before setShowDataViewerModal → modal does not open.
    • deleteGridItem: guard before confirm() → delete prompt does not appear (rather than appearing and then no-op'ing on accept).
    • updateGridItemOrder: guard covers per-tile reorder affordances. react-grid-layout drag-to-reorder is NOT routed through this function, so move/resize/drag remain enabled per R6.
  • DashboardItemDropdown.js accepts new isStreaming prop and applies the Bootstrap disabled state + tooltip ("Editing disabled while dashboard is updating") to:
    • Edit menu item
    • Delete menu item
    • All four Order submenu entries (Bring to Front / Forward / Send Backward / to Back)
  • Copy / Export remain enabled — they don't mutate config and can't race the chatbox.
  • Open edit modals at stream start are NOT auto-closed (accepted v1 UX cost per Scope Boundaries).

Dockerfile bump

  • .devcontainer/Dockerfile sed-swap updated to pin @aquaveo/chatbox-core@0.16.0-beta.0. The workshop image (built from this Dockerfile) will pick up the new engine callback and window events.

Bundle rebuild

  • React bundle rebuilt against the bumped chatbox-core. Keeps non-Docker paths (dev server, jest) in sync. Docker multi-stage build overwrites it at image-build time anyway.

Tests

  • DashboardLoader.streaming.test.js (6 scenarios): initial-mount default false, turn-start/turn-end flips, idempotent duplicate starts, independence from DisabledEditingMovementContext, listener cleanup on unmount.
  • DashboardItem.streaming.test.js (5 scenarios): Edit/Delete disabled class + tooltip when streaming, Copy/Export remain enabled, false→true→false transition, normal (non-streaming) baseline behavior.
  • customRender.js adds StreamingPComponent helper for asserting context value.
  • 11 new tests, all passing. Full test sweep: 2 pre-existing failures in ModuleLoader.test.js (URL encoding, present on feature/tethysdash-mcp-server before my changes); the rest pass (2028+).

How it composes with chatbox-core 0.16.0-beta.0

[user clicks Send in chatbox]
          ↓
chatbox-core@0.16.0-beta.0:
  Chatbox.jsx fires window.CustomEvent('tethysdash:turn-start')
  Chatbox.jsx supplies onToolEnvelope to runChatSession
          ↓
DashboardLoader (this PR) listens, sets isStreaming=true via StreamingContext
          ↓
DashboardItem (this PR) reads isStreaming, disables Edit/Delete/Order
          ↓
[engine fires onToolEnvelope per tool — tile DOM events dispatched per-tool]
          ↓
[turn completes — finally block fires]
          ↓
chatbox-core fires window.CustomEvent('tethysdash:turn-end')
          ↓
DashboardLoader resets isStreaming=false → affordances re-enable

Branching

  • Base: feature/tethysdash-mcp-server (per workspace conventions, NEVER main).
  • Origin: tethysplatform/tethysapp-tethys_dash (never aquaveo).

Test plan

  • npm test -- --testPathPattern='(DashboardLoader|DashboardItem|DashboardLayout)' --no-coverage → 126/126 pass
  • npm run build → bundle compiles
  • After merge + workshop image rebuild, manual smoke: start a chatbox turn that creates multiple tiles; confirm tiles appear incrementally AND edit/delete dropdown items are disabled with the documented tooltip during the turn.

Plan: docs/plans/2026-05-28-002-feat-chatbox-core-incremental-tile-streaming-plan.md
Origin brainstorm: docs/brainstorms/2026-05-28-incremental-tile-streaming-requirements.md
Phase 1 PR (chatbox-core): Aquaveo/chatbox-core#45

romer8 added 3 commits May 28, 2026 15:54
Add a dedicated StreamingContext (Contexts.js) for the chatbox-driven
isStreaming flag. DashboardLoader.js owns the state, listens for the new
tethysdash:turn-start / tethysdash:turn-end window events from
@aquaveo/chatbox-core@>=0.16.0-beta.0, and provides via the new context.

isStreaming lives in its own useState — separate from
disabledEditingMovement — so the existing reset effect at lines 75-79
(which zeroes disabledEditingMovement when isEditing flips false) does
NOT zero isStreaming spuriously.

Dedicated context (rather than extending DisabledEditingMovementContext)
keeps the re-render footprint to DashboardItem consumers only. The
existing 4+ consumers of DisabledEditingMovementContext (Header,
DashboardLayout, PopupLayoutEditor, DashboardItem) do NOT re-render on
every turn boundary — addresses the fan-out concern flagged in the
brainstorm/plan reviews.

Test additions:
- DashboardLoader.streaming.test.js (6 scenarios): initial-mount default
  false, turn-start flips true, turn-end flips back, idempotent duplicate
  starts, independence from DisabledEditingMovementContext, listener
  cleanup on unmount.
- customRender.js adds StreamingPComponent helper for asserting context
  value in tests.

Phase 2 of docs/plans/2026-05-28-002-feat-chatbox-core-incremental-tile-streaming-plan.md
Unit 6.
…eaming

DashboardItem reads isStreaming from the new StreamingContext (Unit 6) and
short-circuits the three config-mutating handlers when true:

- editGridItem: guard fires before setShowDataViewerModal — the edit modal
  does not open at all while the chatbox is mid-turn.
- deleteGridItem: guard fires BEFORE confirm() — the delete confirmation
  modal does not appear (rather than appearing and then no-op'ing on
  acceptance, which would be confusing UX).
- updateGridItemOrder: guard fires before mutating gridItems. Covers the
  per-tile reorder affordances (Bring to Front / Forward / Send to Back /
  Backward in the dropdown) which all delegate here. The react-grid-layout
  drag-to-reorder gesture is NOT routed through this function, so move /
  resize / drag remain enabled per R6.

DashboardItemDropdown propagates isStreaming to its menu items:
- Edit / Delete / Order entries: render with the Bootstrap `disabled` prop
  (rendered-but-disabled, NOT hidden — keeps affordance location stable)
  plus a `title` tooltip ("Editing disabled while dashboard is updating").
- Copy / Export remain enabled — they don't mutate tile config and don't
  conflict with chatbox patch_visualization.

Open edit modals at stream start are NOT auto-closed (accepted v1 UX cost
per Scope Boundaries).

Tests: DashboardItem.streaming.test.js (5 scenarios) covers the disabled-
class + tooltip + handler no-op contract for Edit and Delete, the enabled
state of Copy/Export under streaming, the false→true→false transition, and
normal (non-streaming) baseline behavior.

Note on a11y: react-bootstrap may or may not apply `aria-disabled='true'`
depending on version; the `disabled` class is the jsdom-stable assertion.
Production a11y verification (screen reader announces "Edit, disabled"
with the documented reason) is worth checking after PR lands.

Phase 2 of docs/plans/2026-05-28-002-feat-chatbox-core-incremental-tile-streaming-plan.md
Unit 7.
…ndle

Devcontainer Dockerfile sed-swap updated so the multi-stage build pulls
the new beta from npm. The workshop image (built from this Dockerfile)
will pick up the new onToolEnvelope engine option and the two new
tethysdash:turn-start / tethysdash:turn-end window events that Phase 2
listens for.

React bundle rebuilt against the current chatbox-core file: link (which
itself is at 0.16.0-beta.0). The committed bundle keeps the
non-Docker / dev-server / Jest paths in sync with the source. The
Docker multi-stage build overwrites this bundle at image-build time
anyway — both paths produce the same code now.
@romer8
romer8 merged commit 4441010 into feature/tethysdash-mcp-server May 28, 2026
romer8 added a commit to Aquaveo/ciroh_devcon_2026_workshop that referenced this pull request May 28, 2026
…aming

The v2026.05.6 image (built by publish-image.yml on tag push of
v2026.05.6, ghcr.io/aquaveo/ciroh-devcon-2026:v2026.05.6) bakes
tethysdash @ tip of feature/tethysdash-mcp-server which carries:

  * @aquaveo/chatbox-core 0.16.0-beta.0 — adds the per-tool
    onToolEnvelope engine callback + tethysdash:turn-start /
    tethysdash:turn-end window CustomEvents (Aquaveo/chatbox-core#45).
  * tethysdash StreamingContext + DashboardLoader listener +
    DashboardItem edit/delete/reorder gates
    (tethysplatform/tethysapp-tethys_dash#134).

User-visible behavior changes for workshop participants:
  - Chatbox-created tiles appear incrementally as each MCP tool returns
    (instead of all at once at end-of-turn).
  - Per-tile edit/delete/order dropdown items are disabled during chatbox
    turns with a "Editing disabled while dashboard is updating" tooltip.
  - Layout operations (resize / drag-to-move) stay enabled during turns.
  - Stop button preserves successfully-rendered tiles.

Definitions.md adds a "What you'll notice in the chatbox (image v2026.05.6+)"
section pre-emptively explaining the two visible behaviors so participants
don't read them as bugs.

Participants run `bash scripts/update.sh` on their VMs to pull the new
image. update.sh's existing auto-sync logic propagates the new IMAGE_TAG
from .env.example into their .env, then `docker compose pull` fetches
v2026.05.6 from ghcr and `docker compose up -d --force-recreate` restarts
the stack against the new image. Hard-refresh the browser to load the
new React bundle.
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.

1 participant