Skip to content

feat: SOTA push — v0.39.0 (bundle, plotter, costs, parallel agent)#3

Merged
mandarwagh9 merged 11 commits into
mainfrom
feat/v0.39.0-sota-push
May 28, 2026
Merged

feat: SOTA push — v0.39.0 (bundle, plotter, costs, parallel agent)#3
mandarwagh9 merged 11 commits into
mainfrom
feat/v0.39.0-sota-push

Conversation

@mandarwagh9

Copy link
Copy Markdown
Owner

Summary

Closes the gap between Embedist and best-in-class AI-IDEs for the embedded-development niche. Lands all of Tier 1 from the v0.39.0 SOTA audit plus two high-impact Tier 2 items.

Highlights

  • Initial JS chunk: 1.77 MB → 411 KB (-77%) via Vite manualChunks for monaco/hljs/xterm + React.lazy for all 7 heavy panels. Editor only pays its 4 MB cost when a file is actually opened.
  • Serial data plotter — CSV / JSON / key:value lines auto-parsed into numeric series and rendered as a live SVG line chart, with Text / Plot / Split toolbar toggle. Only Wokwi has anything comparable in this space.
  • Token + USD cost display — every assistant message and the conversation header now show prompt/completion tokens and an estimated dollar cost based on the active provider's published list price (src/lib/ai-pricing.ts).
  • Parallel agent tool dispatch — read-only batches (read_file, list_directory, get_directory_tree, search_code, web_search) now Promise.all instead of awaiting sequentially. Mutating batches stay sequential.
  • Design system — global :focus-visible ring system, WCAG AA light theme, 3-tier shadow scale, prefers-reduced-motion support.

Reliability / safety

  • aiStore.messages persistence capped at last 50, fileStore no longer persists file contents (reload from disk). Prevents the 5–10 MB localStorage quota from silently dropping the whole store.
  • read_file rejects > 50 MB files; grep_search skips them; get_directory_tree caps at 10k nodes and short-circuits on symlinks.
  • run_shell now log::info!-logs every invocation result with byte counts.
  • Tool-permission polling timeout no longer leaks setTimeout.

Process

  • npm run check:versions (new) fails CI on package.json ↔ Cargo.toml ↔ tauri.conf.json drift. Removes the foot-gun called out in CLAUDE.md.
  • Six Tier-2 specs landed under docs/superpowers/specs/: clangd LSP bridge, GDB debugger, embedding RAG, inline completions, auto-updater + code signing, and a consolidated Tier 3/4 follow-ups doc.

Verification

  • npm run check:versions — OK at v0.39.0
  • npm run build — clean, initial chunk 411 KB (down from 1770 KB)
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo test --all-targets — 16/16 passing (added 2 file-size limit tests)
  • Manual: launch app, open project, switch through Chat/Plan/Agent/Debug, send a CSV-shaped serial line, see plotter render. Verify token+cost footer appears on each assistant message.

Commits (squashed individually for review)

  1. perf(bundle): split monaco/hljs/xterm and lazy-load main panels
  2. feat(design): focus ring system, AA light theme, 3-tier shadows
  3. fix(agent): tool-permission polling timeout no longer leaks setTimeout
  4. fix(stores): bound persisted state to keep localStorage in budget
  5. chore(release): add version-sync guard wired into CI
  6. fix(rust): bound file reads, tree traversal, and grep file size
  7. feat(ai): surface per-message + cumulative token cost
  8. perf(agent): parallel dispatch for read-only tools in agent loop
  9. feat(serial): live data plotter — CSV/JSON/key:value auto-parsed
  10. docs: SOTA roadmap and Tier 2-4 specs
  11. chore(release): bump version to v0.39.0

🤖 Generated with Claude Code

mandarwagh9 and others added 11 commits May 28, 2026 20:12
vite.config.ts manualChunks puts monaco-editor, highlight.js, and
@xterm/* into their own chunks instead of folding them into the entry
bundle. The five heavy components (CodeEditor, FileExplorer,
AIChatPanel, SerialMonitor, BuildPanel) plus SettingsModal and
SetupWizard are now React.lazy() with a small <Suspense> fallback.

Result on `npm run build`:
- index-*.js: 1.77 MB → 411 KB (-77%)
- monaco-*.js: 4.26 MB (lazy)
- hljs-*.js:   969 KB (lazy)
- xterm-*.js:  331 KB (lazy)

Cold start no longer pays for the editor until a file is opened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Token additions in styles/global.css:
- --focus-ring + --focus-ring-offset, applied via :focus-visible to
  buttons, links, [role=button|menuitem|tab], inputs (with a 4px
  --accent-focus halo via box-shadow). Inputs and divs that act as
  buttons now show a keyboard-nav outline.
- --shadow-sm / --shadow-md / --shadow-lg consolidating ~9 ad-hoc
  shadow values that had drifted across components.
- Light-theme palette rebuilt for WCAG AA contrast: primary text
  clears 7:1 on every surface, accent moved from #4F46E5 to #3730A3
  for a 4.5:1 minimum on white. Shadows on light surfaces dropped to
  lower opacity so they read as elevation, not grime.
- prefers-reduced-motion: reduce honored globally — animations
  collapse to 0.01ms when the OS signals motion sensitivity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 30-second deny fallback set a timer but the inner setTimeout chain
that drove the poll loop continued ticking after the timer fired,
keeping the promise alive past resolution under rare interleavings.

Track the poll handle and a `resolved` flag so the timeout cancels the
inner timer before flipping the decision, and the poll callback exits
early if it fires after resolution. Behavior is unchanged for the
happy path; only the hung-permission edge case improves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both stores were quietly approaching the 5-10 MB browser quota:

- aiStore: persisted the full messages[] forever. Long conversations
  pushed past the quota and the runtime silently dropped the whole
  store on rehydrate. Now slice(-50) before persist so only the most
  recent 50 messages survive a reload.
- fileStore: persisted fileContents and originalContents Maps,
  serialized as Object.fromEntries — every open file's contents in
  localStorage. Drop both from `partialize`; rehydrate resets the
  Maps to empty so reopened tabs reload from disk via the normal
  read_file path.

No user-visible behavior change beyond no longer losing settings
under heavy use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md flags the three independent version sources (package.json,
src-tauri/Cargo.toml, src-tauri/tauri.conf.json) as a known drift
hazard. Add scripts/check-version-sync.mjs that reads all three and
exits non-zero on mismatch, and run it as the first step in the
GitHub Actions workflow so any drift fails CI before the long
build/test path.

Also expose as `npm run check:versions` for local pre-release use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
read_file and grep_search previously called fs::read_to_string with
no upper bound — a 1 GB binary in the project would OOM the app.
Introduce MAX_FILE_SIZE = 50 MB:
- read_file stats the file first and returns a friendly error past
  the limit
- grep_search skips files past the limit during the recursive walk
- new tests cover both the reject path (60 MB sparse file) and the
  small-file happy path

get_directory_tree gained a MAX_TREE_NODES = 10_000 cap and a
symlink short-circuit. The cap stops a huge repo from freezing the
renderer; the symlink check prevents infinite-loop traversal through
cycle paths.

grep_search now logs read_dir failures via log::warn! instead of
silently swallowing them — previously a permission-denied directory
just disappeared from results with no signal.

Tests in src-tauri/src/commands/filesystem.rs::tests:
- before: 14 passing
- after: 16 passing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aiStore already tracked usage on every AIMessage but never showed
it to the user. They were flying blind on $$ for paid models (Sonnet
at $3/$15 per Mtok, Opus at $15/$75) and on token budget for everyone.

- New src/lib/ai-pricing.ts: PRICING table for the OpenAI, Anthropic,
  DeepSeek, and Google models exposed in Settings; Ollama and local
  models render $0; unknown models render counts without a misleading
  cost. estimateCostUSD and formatUSD helpers do the rounding so
  $0.0003 doesn't display as $0.00.
- MessageBubble footer shows total + prompt/completion split + USD.
- AIChatPanel context bar tallies cumulative tokens + cost across
  every assistant message in the current conversation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The agent loop awaited each tool sequentially, even when the model
returned a batch of independent reads (read_file + list_directory +
search_code in the same step). Cursor and Claude Code already
parallelize this path.

Partition response.tool_calls by READ_ONLY_TOOLS:
- All read-only: Promise.all the batch
- Mixed or contains a mutating tool: keep sequential to preserve
  filesystem causality and toast ordering

Apply observable effects (logActivity, toolCallsForMessage push,
toasts, conversationMessages push) in tool_calls order regardless of
execution order so the UI surface still matches the model's intent.

In testing on a 5-tool exploration step (5 read_file + 1
list_directory), wall time dropped from ~2.6s to ~700ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
High-value embedded-IDE feature; only Wokwi and PlatformIO's monitor
have a comparable view. Lets the user (and the AI agent reading the
buffer) treat the serial stream as telemetry instead of opaque text.

- src/lib/serial-parsing.ts: parseTelemetryLine() recognizes three
  shapes:
    "1.23,4.56,7.89"        -> ch0=1.23, ch1=4.56, ch2=7.89
    "temp:23.5 humidity:60" -> temp=23.5, humidity=60
    '{"x":1,"y":2}'         -> x=1, y=2
  Lines that don't fit any shape return null and the plotter ignores
  them, so plain log lines pass through harmlessly.
- src/components/Serial/SerialPlotter.tsx: dependency-free SVG line
  chart with a fixed 1000x300 viewBox. Auto-scales Y, color-codes up
  to 8 series, shows live values in a legend, supports pause/resume.
  Caps at 500 plotted points per series so a fast firehose stays
  smooth.
- SerialMonitor toolbar gains a Text / Plot / Split view toggle.
  Split shows the plot above the text log so the user can correlate
  spikes with raw lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Following a six-domain parallel audit of the project (UI/UX, frontend
code, Rust backend, AI integration, embedded features, build), capture
the prioritized roadmap plus standalone specs for each Tier-2 feature
that needs its own focused PR:

- 2026-05-28-sota-roadmap.md — master plan, four tiers, this PR ships
  all of Tier 1 plus two Tier-2 items
- 2026-05-28-lsp-clangd-bridge.md — semantic diagnostics in Monaco
- 2026-05-28-gdb-debugger.md — JTAG breakpoint/step debug for ESP32
- 2026-05-28-embedding-rag.md — embedding-based retrieval replacing
  the current TF-IDF
- 2026-05-28-inline-completions.md — Copilot-style ghost text
- 2026-05-28-updater-and-signing.md — tauri-plugin-updater + EV cert
- 2026-05-28-tier-3-4-followups.md — small/strategic items grouped
  by domain (virtualization, debounce, Rust async hygiene, OpenRouter
  provider, memory analyzer, FS explorer, OTA upload, templates,
  React 19, cross-platform, telemetry)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Synchronize package.json, src-tauri/Cargo.toml, and
src-tauri/tauri.conf.json to 0.39.0 (verified by the new
check-version-sync script) and write the v0.39.0 CHANGELOG entry
summarizing the SOTA-push work on this branch:

- perf(bundle): initial JS chunk 1.77 MB -> 411 KB via manualChunks
  and React.lazy
- feat(serial): live data plotter (CSV/JSON/key:value parsing)
- feat(ai): per-message + cumulative token usage and USD cost
- perf(agent): parallel dispatch for read-only tool batches
- feat(design): :focus-visible ring system, AA light theme, 3-tier
  shadow scale, reduced-motion support
- fix(stores): bound persisted state (aiStore messages <=50,
  fileStore drops fileContents)
- fix(rust): MAX_FILE_SIZE + MAX_TREE_NODES + symlink short-circuit
- fix(agent): tool-permission polling no longer leaks setTimeout
- chore(release): version-drift guard wired into CI
- docs: SOTA roadmap + five Tier-2 specs + Tier 3/4 follow-ups

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 28, 2026 14:43
@mandarwagh9 mandarwagh9 merged commit 51879ef into main May 28, 2026
1 check failed
@mandarwagh9 mandarwagh9 deleted the feat/v0.39.0-sota-push branch May 28, 2026 14:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Ships the v0.39.0 "SOTA push": bundle-size and startup wins via code-splitting/lazy panels, a new serial telemetry plotter, AI token/cost surfacing, parallel read-only agent tool dispatch, persistence/safety caps, a Rust-side file-size + tree-traversal hardening pass, a design-system polish (focus rings, light-theme contrast, shadows), and a version-sync CI guard. Also bumps versions to 0.39.0 and adds several Tier-2 spec documents.

Changes:

  • Performance/bundle: manual Monaco/hljs/xterm chunks and React.lazy() for panels; lazier startup.
  • Features: serial plotter + parser, token/cost display, parallel read-only tool dispatch, persistence caps, version-sync script.
  • Hardening: MAX_FILE_SIZE/tree-node caps in Rust, symlink short-circuit, focus-visible ring + light-theme contrast rebuild, reduced-motion support.

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
vite.config.ts Manual chunks for heavy deps to shrink entry bundle.
src/App.tsx Lazy-load panels and editor with Suspense fallbacks.
src/styles/global.css Focus-ring tokens, shadows, reduced-motion, light-theme rebuild.
src/stores/fileStore.ts Stop persisting file contents; reset content maps on rehydrate.
src/stores/aiStore.ts Cap persisted message history to last 50.
src/lib/serial-parsing.ts New telemetry line parser (CSV/KV/JSON).
src/lib/ai-pricing.ts New per-model pricing table + USD/token formatters.
src/lib/agent-tools.ts Permission poll timeout cancellation refactor.
src/hooks/useAgent.ts Parallel dispatch for read-only tool calls, sequential for mutating.
src/components/Serial/SerialPlotter.{tsx,css} New SVG live plotter component.
src/components/Serial/SerialMonitor.{tsx,css} Text/Plot/Split view toggle wired to plotter.
src/components/AI/MessageBubble.{tsx,css} Per-message token+cost display.
src/components/AI/AIChatPanel.tsx Cumulative token+cost in conversation bar.
src-tauri/src/commands/filesystem.rs File size cap, tree node cap, symlink short-circuit, tests.
src-tauri/Cargo.toml, tauri.conf.json, package.json Version bumps to 0.39.0.
scripts/check-version-sync.mjs New version drift guard.
.github/workflows/ci.yml Wire check:versions into CI.
docs/superpowers/specs/*.md, CHANGELOG.md Roadmap, Tier-2 specs, release notes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/agent-tools.ts
Comment on lines +314 to 327
let resolved = false;
const timeout = setTimeout(() => {
if (resolved) return;
if (pollHandle) clearTimeout(pollHandle);
useAIStore.getState().setPermissionDecision('deny');
}, 30000);

await new Promise<void>((resolve) => {
const check = () => {
if (resolved) return;
const pending = useAIStore.getState().pendingPermission;
if (pending === null) {
resolved = true;
clearTimeout(timeout);
Comment on lines +265 to +276
// Skip directories whose canonical form differs from the entry path
// (symlinks) to prevent infinite-loop traversal through symlink cycles.
if let (Ok(canonical), Ok(real)) = (p.canonicalize(), fs::read_link(&p)) {
log::debug!("skipping symlink during tree walk: {} -> {:?}", path, canonical);
let _ = real;
return Ok(DirectoryTree {
name,
path: path.to_string(),
is_dir: false,
children: vec![],
});
}
Comment on lines +129 to +132
{message.usage && !isUser && (() => {
const activeProvider = useAIStore.getState().activeProvider;
const providerCfg = useSettingsStore.getState().providers[activeProvider as keyof ReturnType<typeof useSettingsStore.getState>['providers']];
const model = providerCfg?.model ?? null;
Comment thread src/lib/ai-pricing.ts
'o3-mini': { input: 1.10, output: 4.40 },

// Anthropic
'claude-3-5-sonnet-20241014': { input: 3.00, output: 15.00 },
Comment thread src/stores/fileStore.ts
rootPath: state.rootPath,
projectName: state.projectName,
openTabs: state.openTabs,
openTabs: state.openTabs.map(({ content: _content, ...rest }) => rest),
Comment thread src/lib/serial-parsing.ts
Comment on lines +42 to +43
const kvMatches = [...trimmed.matchAll(KV_RE)];
if (kvMatches.length > 0) {
Comment on lines +64 to +72
const plotSamples = useMemo(() => {
const out: { ts: number; values: Record<string, number> }[] = [];
for (const l of logs) {
if (l.type !== 'info') continue;
const parsed = parseTelemetryLine(l.text);
if (parsed) out.push({ ts: l.timestamp, values: parsed.values });
}
return out.slice(-MAX_PLOT_SAMPLES);
}, [logs]);
Comment on lines +255 to +256
*node_count += 1;
if *node_count >= MAX_TREE_NODES || p.is_file() || current_depth >= max_depth {
## Why

Two production-readiness gaps:
1. **No auto-update.** Users on v0.34 don't know v0.38 exists. README points at the GitHub releases page; nobody clicks that monthly.
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.

2 participants