Build trace viewer mini-app HTML/CSS/JS

t-272.3·WorkTask·
·
·
Parent:t-272·Created2 months ago·Updated2 months ago

Dependencies

Description

Edit

Create the HTML/CSS/JS for the trace viewer UI.

Context

This is the frontend that users see when they click a trace link from Telegram. It opens in Telegram's in-app browser, so it must be mobile-friendly.

Requirements

  • Single HTML file with embedded CSS and JS (no build step, no npm)
  • Mobile-first responsive design
  • Works in Telegram's in-app WebView browser
  • Dark/light mode based on prefers-color-scheme

UI Layout

Header

  • Tool name (e.g., 'python_exec')
  • Timestamp
  • Duration (e.g., '1.2s')

Input Section (collapsible)

  • Expandable/collapsible with click
  • JSON syntax highlighting
  • Copy button

Output Section (collapsible)

  • Same as input
  • For large outputs, consider max-height with scroll

Footer

  • 'Trace expires in X days' note

Implementation Options

Option A: Server-rendered (simpler)

HTML page includes trace data directly via template substitution. Server replaces {{tool_name}}, {{input}}, {{output}}, etc.

Option B: Client-fetched (more flexible)

HTML page fetches /api/trace/:id on load. Trace ID passed via URL or data attribute.

Recommend Option A for simplicity.

Syntax Highlighting

Use a lightweight solution:

  • highlight.js CDN (small JSON subset): ~10KB
  • Or simple CSS-based formatting (just colors for strings/numbers/keys)
  • Or no highlighting (just monospace + whitespace formatting)

Start simple, can enhance later.

File Location

If embedding in Haskell: define as Text constant in Omni/Ava/Web.hs If loading from file: Omni/Ava/Web/trace.html

Example HTML Structure

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Trace: {{tool_name}}</title> <style> /* Mobile-first CSS */ :root { --bg: #1a1a2e; --fg: #eee; --accent: #4a9eff; } @media (prefers-color-scheme: light) { :root { --bg: #fff; --fg: #222; --accent: #0066cc; } } body { font-family: system-ui; background: var(--bg); color: var(--fg); padding: 1rem; } .section { margin: 1rem 0; border: 1px solid var(--accent); border-radius: 8px; } .section-header { padding: 0.75rem; cursor: pointer; } .section-content { padding: 1rem; display: none; overflow-x: auto; } .section.open .section-content { display: block; } pre { margin: 0; white-space: pre-wrap; word-break: break-word; } .copy-btn { float: right; } </style> </head> <body> <h1>{{tool_name}}</h1> <p>{{timestamp}} · {{duration_ms}}ms</p>

<div class="section open"> <div class="section-header">Input <button class="copy-btn">Copy</button></div> <div class="section-content"><pre>{{input_json}}</pre></div> </div>

<div class="section open"> <div class="section-header">Output <button class="copy-btn">Copy</button></div> <div class="section-content"><pre>{{output_json}}</pre></div> </div>

<script> document.querySelectorAll('.section-header').forEach(h => { h.addEventListener('click', e => { if (!e.target.classList.contains('copy-btn')) h.parentElement.classList.toggle('open'); }); }); document.querySelectorAll('.copy-btn').forEach(btn => { btn.addEventListener('click', () => { const text = btn.closest('.section').querySelector('pre').textContent; navigator.clipboard.writeText(text); btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = 'Copy', 1500); }); }); </script> </body> </html>

Testing

Test in:

  • Mobile browser (Chrome/Safari)
  • Telegram in-app browser (send yourself a link)
  • Desktop browser

Timeline (1)

🔄[human]Open → Done2 months ago