Planned Effort
8 story points — sprint item #1 (High): Frontend sessions.js dispatch registry decomposition
Depends on: Monday PR merged (or branch based on Monday) — no hard code dependency, but finish policy docs first if review is parallel.
Problem
static/js/sessions.js is a ~427-line monolith. Tool invocations (renderToolUse) and tool results (renderToolResult) are implemented as long if / else if chains keyed by tool name / result_type. Every new Claude tool type requires editing this file — high regression risk and unlike the backend, which already uses an ordered dispatch table in utils/tool_dispatch.py (PR #56).
Goal
Mirror the backend pattern on the frontend:
- Registry maps tool type → render function.
- One module per renderer (or small grouped modules).
sessions.js retains only workspace/session orchestration (target: < 100 lines of rendering logic; imports + loop + renderUser / renderAssistant / renderSystem may remain in file or move per taste).
- Zero visual/behavior change for existing sessions.
- Vitest tests prove registry wiring + sample renderers.
Scope
Target layout (suggested)
static/js/
sessions.js # orchestration: loadSession, message loop
render/
registry.js # TOOL_USE_RENDERERS, TOOL_RESULT_RENDERERS, dispatch helpers
tool_use/
bash.js
read.js
write.js
edit.js
glob.js
grep.js
task.js
todo_write.js
ask_user_question.js
fallback.js # JSON.stringify fallback (current else branch)
tool_result/
bash.js
file_read.js
file_edit.js
...
plan.js
fallback.js
Names are flexible; keep ES module import/export consistent with static/js/app.js.
Tool use renderers to preserve (from current sessions.js)
| Tool name |
Notes |
Bash |
Command + optional description |
Read, Write, Edit |
File paths + content snippets |
Glob, Grep |
Pattern + path |
Task |
subagent_type, description, prompt |
TodoWrite |
todo list icons |
AskUserQuestion |
questions |
| default |
Truncated JSON (truncate(s, 500)) — tools like WebFetch / WebSearch may use this path today |
Also preserve getToolSummary(name, inp) — move next to registry or into registry.js.
Tool result renderers to preserve (result_type)
result_type |
Notes |
bash |
stdout/stderr, exit status |
file_read, file_edit, file_write |
|
glob, grep, web_search, web_fetch |
|
task |
status, duration, retrieval, description branches |
todo_write, user_input, plan |
|
| default |
Tool result (${rt}) summary-only when no body |
Security (non-negotiable)
- Use
esc() from shared/utils.js for string interpolation into HTML.
- Route markdown through
renderMarkdown() in shared/markdown.js (DOMPurify) — never marked.parse() → innerHTML directly for untrusted session text.
- Do not weaken
static/js/shared/markdown.test.js expectations.
Tests (Vitest)
New file(s) e.g. static/js/render/registry.test.js:
Run: npm test
Docs
- Update
docs/architecture.md frontend section: mention render/registry.js pattern and parity with utils/tool_dispatch.py.
Acceptance Criteria
Planned Effort
8 story points — sprint item #1 (High): Frontend
sessions.jsdispatch registry decompositionDepends on: Monday PR merged (or branch based on Monday) — no hard code dependency, but finish policy docs first if review is parallel.
Problem
static/js/sessions.jsis a ~427-line monolith. Tool invocations (renderToolUse) and tool results (renderToolResult) are implemented as longif / else ifchains keyed by tool name /result_type. Every new Claude tool type requires editing this file — high regression risk and unlike the backend, which already uses an ordered dispatch table inutils/tool_dispatch.py(PR #56).Goal
Mirror the backend pattern on the frontend:
sessions.jsretains only workspace/session orchestration (target: < 100 lines of rendering logic; imports + loop +renderUser/renderAssistant/renderSystemmay remain in file or move per taste).Scope
Target layout (suggested)
Names are flexible; keep ES module
import/exportconsistent withstatic/js/app.js.Tool use renderers to preserve (from current
sessions.js)BashRead,Write,EditGlob,GrepTaskTodoWriteAskUserQuestiontruncate(s, 500)) — tools likeWebFetch/WebSearchmay use this path todayAlso preserve
getToolSummary(name, inp)— move next to registry or intoregistry.js.Tool result renderers to preserve (
result_type)result_typebashfile_read,file_edit,file_writeglob,grep,web_search,web_fetchtasktodo_write,user_input,planTool result (${rt})summary-only when no bodySecurity (non-negotiable)
esc()fromshared/utils.jsfor string interpolation into HTML.renderMarkdown()inshared/markdown.js(DOMPurify) — nevermarked.parse()→innerHTMLdirectly for untrusted session text.static/js/shared/markdown.test.jsexpectations.Tests (Vitest)
New file(s) e.g.
static/js/render/registry.test.js:getToolSummary('Bash', { command: 'ls' })matches pre-refactor output (snapshot or string assert).Run:
npm testDocs
docs/architecture.mdfrontend section: mentionrender/registry.jspattern and parity withutils/tool_dispatch.py.Acceptance Criteria
sessions.jsreduced to orchestration-focused size (< 100 lines for tool rendering chains removed)npm testandpytest -qgreen in CI