timeplus-app-builder
Build real-time Timeplus data processing and analysis applications. Creates pure frontend HTML/JavaScript apps that connect directly to Timeplus Proton via @...
Description
name: timeplus-app-builder description: Build real-time Timeplus data processing and analysis applications. Creates pure frontend HTML/JavaScript apps that connect directly to Timeplus Proton via @timeplus/proton-javascript-driver (UMD), visualize live streaming data with @timeplus/vistral (UMD), and follow the Timeplus UI style guide. No npm build or bundler required — output is a single self-contained HTML file. license: Apache-2.0 compatibility: pulsebot>=0.1.0 allowed-tools: shell, workspace, file_ops
Timeplus App Builder
Use this skill whenever the user asks to build a data processing application, pipeline visualizer, real-time dashboard, streaming analytics app, or any frontend tool that queries or visualizes data from Timeplus Proton.
Overview
You will produce a single self-contained HTML file that:
- Queries Timeplus Proton directly via
@timeplus/proton-javascript-driverloaded from unpkg CDN - Visualizes streaming data using
@timeplus/vistralloaded from unpkg CDN - Follows the Timeplus App Style Guide (dark theme, brand colors, clean layout)
- Requires no npm install, no build step — open the HTML file directly in a browser
Key architecture: Both
@timeplus/proton-javascript-driverand@timeplus/vistralship UMD builds. Import them via<script src="https://unpkg.com/...">tags. The Proton driver connects directly to the agent proxy running atlocalhost:8001.
Step-by-Step Workflow
Step 1 — Clarify Requirements
Before writing any code, confirm:
- What stream(s) or table(s) to query (name, schema if known)
- What kind of visualization: time series, bar/column, table, single value, or multi-panel
- Whether the query is streaming (
SELECT ... FROM stream_name) or historical (SELECT ... FROM table(stream_name)) - Any filters, aggregations, or window functions needed
If the user doesn't know the schema, suggest running DESCRIBE stream_name or SHOW STREAMS first.
Step 2 — Use the HTML Template
All Timeplus apps follow this single-file HTML template. Read references/STYLE_GUIDE.md for style rules and references/VISTRAL_API.md for chart configuration options.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Timeplus App</title>
<!-- 1. React (required by Vistral) -->
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- 2. Vistral peer dependencies — load BEFORE vistral -->
<script src="https://unpkg.com/lodash@4/lodash.min.js"></script>
<script src="https://unpkg.com/ramda@0.29/dist/ramda.min.js"></script>
<script src="https://unpkg.com/@antv/g2@5/dist/g2.min.js"></script>
<script src="https://unpkg.com/@antv/s2@2/dist/s2.min.js"></script>
<script>
// Prevent jsx-runtime from crashing on process.env access
window.process = window.process || {
env: { NODE_ENV: "production" }
};
// Some builds also expect global
window.global = window.global || window;
</script>
<!-- 3. Vistral UMD — exposes window.Vistral -->
<script src="https://unpkg.com/@timeplus/vistral/dist/index.umd.min.js"></script>
<!-- 4. Proton JavaScript Driver UMD — exposes window.ProtonDriver -->
<script src="https://unpkg.com/@timeplus/proton-javascript-driver/dist/index.umd.js"></script>
<style>
/* Timeplus design tokens */
:root {
--tp-bg-primary: #0f1117;
--tp-bg-secondary: #1a1d27;
--tp-bg-card: #1e2235;
--tp-bg-hover: #252a3a;
--tp-accent-primary: #7c6af7;
--tp-accent-secondary: #4fc3f7;
--tp-accent-success: #4caf82;
--tp-accent-warning: #f7a84f;
--tp-accent-danger: #f76f6f;
--tp-text-primary: #e8eaf6;
--tp-text-secondary: #9ea3b8;
--tp-text-muted: #5c6380;
--tp-border: #2e3450;
--tp-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
--tp-font-sans: 'Inter', system-ui, sans-serif;
--tp-radius-lg: 12px;
--tp-shadow: 0 2px 12px rgba(0,0,0,0.4);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--tp-bg-primary); color: var(--tp-text-primary); font-family: var(--tp-font-sans); font-size: 14px; line-height: 1.5; }
.tp-app { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.tp-header { display: flex; align-items: center; gap: 12px; padding: 0 24px; height: 56px; background: var(--tp-bg-secondary); border-bottom: 1px solid var(--tp-border); flex-shrink: 0; }
.tp-header-title { font-size: 16px; font-weight: 600; }
.tp-header-badge { font-size: 11px; padding: 2px 8px; background: var(--tp-accent-primary); color: white; border-radius: 99px; font-weight: 500; }
.tp-status { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--tp-text-muted); margin-left: auto; }
.tp-status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--tp-text-muted); }
.tp-status-dot.connected { background: var(--tp-accent-success); }
.tp-status-dot.error { background: var(--tp-accent-danger); }
.tp-main { flex: 1; overflow: auto; padding: 20px 24px; display: grid; gap: 16px; }
.tp-card { background: var(--tp-bg-card); border: 1px solid var(--tp-border); border-radius: var(--tp-radius-lg); padding: 16px; box-shadow: var(--tp-shadow); }
.tp-card-title { font-size: 13px; font-weight: 600; color: var(--tp-text-secondary); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 12px; }
.tp-error { background: rgba(247,111,111,0.1); border: 1px solid var(--tp-accent-danger); border-radius: 8px; padding: 12px 16px; color: var(--tp-accent-danger); font-size: 13px; display: none; }
.chart-container { min-height: 300px; }
</style>
</head>
<body>
<div class="tp-app">
<header class="tp-header">
<span class="tp-header-title">My Stream App</span>
<span class="tp-header-badge">LIVE</span>
<span class="tp-status">
<span class="tp-status-dot" id="status-dot"></span>
<span id="status-text">Connecting…</span>
</span>
</header>
<main class="tp-main">
<div class="tp-error" id="error-panel"></div>
<div class="tp-card">
<div class="tp-card-title">Live Stream</div>
<div class="chart-container" id="chart"></div>
</div>
</main>
</div>
<script>
// ---- Library globals ----
// proton-javascript-driver UMD exposes window.ProtonDriver
const { ProtonClient } = window.ProtonDriver;
// vistral UMD exposes window.Vistral; React/ReactDOM are also on window
const { Vistral, React, ReactDOM } = window;
const { StreamChart } = Vistral;
// ---- Configuration ----
const SQL = 'SELECT * FROM my_stream'; // TODO: replace with your SQL
// Connect to the Proton proxy running on the agent at localhost:8001
const client = new ProtonClient({ host: 'localhost', port: 8001 });
// ---- State ----
let columns = [];
let rows = [];
let chartRoot = null;
// ---- Helpers ----
function setStatus(state, message) {
document.getElementById('status-dot').className = 'tp-status-dot ' + (state || '');
document.getElementById('status-text').textContent = message;
}
function showError(msg) {
const panel = document.getElementById('error-panel');
panel.style.display = msg ? 'block' : 'none';
panel.textContent = msg || '';
if (msg) setStatus('error', 'Error');
}
function inferColumns(row) {
return Object.entries(row).map(([name, value]) => ({
name,
type: typeof value === 'number' ? 'float64'
: String(value).match(/^\d{4}-\d{2}-\d{2}/) ? 'datetime64'
: 'string',
}));
}
// ---- Render chart ----
function renderChart() {
if (columns.length === 0) return;
if (!chartRoot) chartRoot = ReactDOM.createRoot(document.getElementById('chart'));
// TODO: adjust config for your data — see references/VISTRAL_API.md
const config = {
chartType: 'line',
xAxis: columns[0].name,
yAxis: columns[1]?.name ?? columns[0].name,
temporal: { mode: 'axis', field: columns[0].name, range: 60 },
};
chartRoot.render(
React.createElement(StreamChart, {
config,
data: { columns, data: rows, isStreaming: true },
theme: 'dark',
})
);
}
// ---- Main query loop ----
async function runQuery() {
try {
setStatus('', 'Connecting…');
const { rows: stream, abort } = await client.query(SQL);
setStatus('connected', 'Connected');
// Expose abort so it can be called externally if needed
window.stopQuery = abort;
for await (const row of stream) {
if (columns.length === 0) columns = inferColumns(row);
rows = [...rows.slice(-999), row]; // keep last 1000 rows
renderChart();
}
setStatus('', 'Stream ended');
} catch (err) {
showError(err.message);
}
}
runQuery();
</script>
</body>
</html>
Step 3 — Proton Connection
Read references/PROTON_DRIVER.md for the full driver API.
The driver connects directly to the Proton proxy at localhost:8001. UMD global: window.ProtonDriver.
const { ProtonClient } = window.ProtonDriver;
const client = new ProtonClient({ host: 'localhost', port: 8001 });
const { rows, abort } = await client.query('SELECT * FROM my_stream');
for await (const row of rows) {
// row is a plain JavaScript object: { col1: val1, col2: val2, ... }
console.log(row);
}
// To cancel a running streaming query:
abort();
Step 4 — Visualization with Vistral
Read references/VISTRAL_API.md for the complete component reference.
Vistral UMD global: window.Vistral. Key components: StreamChart, SingleValueChart, DataTable.
All charts are rendered via React (React.createElement). Always pass theme="dark".
Quick chart config reference:
| Chart type | chartType |
Best for | temporal mode |
|---|---|---|---|
| Time series | 'line' or 'area' |
Scrolling metrics over time | 'axis' |
| Horizontal bars | 'bar' |
Ranked categories, latest snapshot | 'frame' |
| Vertical bars | 'column' |
Categorical comparison | 'frame' |
| KPI tile | 'singleValue' |
One big number | 'frame' |
| Live table | 'table' |
Raw rows / mutable state | 'key' |
| Geo points | 'geo' |
Location data | 'key' |
Step 5 — Apply Timeplus Style Guide
Read references/STYLE_GUIDE.md for full rules. Core rules always applied:
- Dark background
#0f1117for page,#1e2235for cards - Purple
#7c6af7for primary accents - Inter font for UI, monospace for data values
- Cards with
border-radius: 12pxand border#2e3450 - Header 56px with LIVE badge and connection status indicator
Step 6 — Deliver the App
- Write the complete single-file HTML, filling in the correct SQL and chart config
- use workspace_create_app to create the app
- Present the file — it opens directly in a browser with no server or build step needed
For PulseBot deployment:
workspace_create_app(
session_id=session_id,
task_name="My Stream App",
html=open("my-app.html").read()
)
SQL Patterns
Load the timeplus-sql-guide skill for all SQL questions:
- Streaming vs historical queries
- Window functions (tumble, hop, session)
- Aggregations, joins, filters
- Schema introspection (
DESCRIBE,SHOW STREAMS)
Checklist Before Delivering
- Single HTML file — no separate JS/CSS files, no package.json, no build step
- Script load order: React → ReactDOM → lodash → ramda → @antv/g2 → @antv/s2 → vistral → proton-javascript-driver
-
window.ProtonDriverdestructured forProtonClient -
window.Vistraldestructured forStreamChart(and other components as needed) -
ProtonClientconfigured with{ host: 'localhost', port: 8001 } - SQL uses correct Proton streaming syntax
-
columnsinferred from first received row usinginferColumns() - Chart
config.temporaluses correctmodefor the query type - Status indicator updates on connect, stream end, and error
-
abort()stored onwindow.stopQueryor similar - CSS uses Timeplus design tokens (CSS variables) from the style template
Reviews (0)
No reviews yet. Be the first to review!
Comments (0)
No comments yet. Be the first to share your thoughts!