Dataface Tasks

Split shared cloud chat UI into stream render and format modules

IDCLOUD_SUITE-SPLIT_SHARED_CLOUD_CHAT_UI_INTO_STREAM_RENDER_AND_FORMAT_MODULES
Statuscompleted
Priorityp2
Milestonem1-ft-analytics-analyst-pilot
Ownerui-design-frontend-dev
Completed bydave
Completed2026-03-22

Problem

Refactor apps/cloud/static/js/chat/chat.js into smaller modules that separate SSE transport, message/tool rendering, and text formatting. Remove stale tool-surface assumptions and make the shared chat component easier to extend without growing one file that mixes protocol, DOM updates, and markdown-ish formatting.

Context

apps/cloud/static/js/chat/chat.js (383 lines) is the shared chat component used by four consumers: - apps/cloud/static/js/dashboard/init.js (JS import) - apps/cloud/templates/dashboards/dashboard_edit.html (inline script) - apps/cloud/templates/charts/chart_editor.html (inline script) - apps/cloud/templates/chat/org_chat_home.html (inline script)

All import { initChat } from chat/chat.js. The file mixes three concerns: 1. Text formattingescapeHtml, formatMessage (markdown-ish transforms) 2. DOM renderingTOOL_DISPLAY map, createDataTable, thinking indicators, tool call display, message elements 3. SSE transport — fetch + ReadableStream reader, line-based SSE parsing, event dispatch

No build step or bundler — files are served as ES modules from Django static paths.

Possible Solutions

A. Extract three sibling modules, keep chat.js as orchestrator (Recommended) Split into format.js, render.js, stream.js alongside chat.js. The orchestrator imports from all three and exports initChat unchanged. Zero consumer changes needed. + Clean separation by concern; each module is independently testable + No new abstractions — just moving existing functions - Four files instead of one (acceptable for clarity)

B. Two modules (format.js + everything else) Lighter touch but leaves rendering and transport interleaved. - Doesn't address the main pain: DOM + protocol mixed together

C. Class-based refactor Wrap state in a ChatSession class. - Adds abstraction that doesn't earn its place (anti-slop). The closure in initChat already scopes state fine.

Plan

Selected: Option A — three modules + thin orchestrator.

Files to create: - apps/cloud/static/js/chat/format.jsescapeHtml, formatMessage - apps/cloud/static/js/chat/render.jsTOOL_DISPLAY, createDataTable, createRenderer(containerEl, opts) returning bound DOM helpers - apps/cloud/static/js/chat/stream.jsconnectSSE(url, body, csrfToken, onEvent) handling fetch + ReadableStream + line parsing

Files to modify: - apps/cloud/static/js/chat/chat.js — slim to orchestrator importing the three modules, wiring them together in initChat. Export signature unchanged.

No consumer changes needed — initChat API stays identical.

Implementation Progress

Created modules

  • format.js (44 lines) — escapeHtml, formatMessage. Pure functions, no DOM state.
  • render.js (161 lines) — TOOL_DISPLAY map, createDataTable (private), createRenderer(containerEl, opts) factory returning bound DOM helpers: addMessage, addError, createThinkingIndicator, updateThinkingStatus, createToolCallDisplay, updateToolResult, scrollToBottom.
  • stream.js (55 lines) — connectSSE(url, body, csrfToken, onEvent) handles fetch + ReadableStream + line-based SSE parsing. Calls onEvent for each parsed JSON event.

Modified files

  • chat.js (136 lines, down from 383) — thin orchestrator. Imports from the three modules. initChat creates a renderer, wires SSE events to renderer calls, binds input events. Exports { initChat } with identical signature and return type { addMessage, sendMessage }.

Key decisions

  • createRenderer returns an object of bound closures rather than a class — matches the existing closure style in initChat and avoids adding abstraction.
  • addError extracted as a renderer method to avoid duplicating the error-div pattern in the orchestrator.
  • content streaming case still directly creates the assistant div in the orchestrator (not via addMessage) because it needs the incremental-update reference — moving this to render would require extra state tracking with no benefit.

Validation

  • All four consumers import only { initChat } from chat/chat.js — no import path changes needed.
  • chart_editor.html uses chat.addMessage(...) — preserved via renderer.addMessage in the return object.
  • just fix passes (black + ruff).

QA Exploration

  • [x] QA exploration completed — pure refactor, no visual/behavioral changes. Manual browser verification recommended before merge.

Review Feedback

  • [ ] Review cleared