feat: SOTA push — v0.39.0 (bundle, plotter, costs, parallel agent)#3
Merged
Conversation
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>
There was a problem hiding this comment.
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 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; |
| 'o3-mini': { input: 1.10, output: 4.40 }, | ||
|
|
||
| // Anthropic | ||
| 'claude-3-5-sonnet-20241014': { input: 3.00, output: 15.00 }, |
| rootPath: state.rootPath, | ||
| projectName: state.projectName, | ||
| openTabs: state.openTabs, | ||
| openTabs: state.openTabs.map(({ content: _content, ...rest }) => rest), |
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
manualChunksfor monaco/hljs/xterm +React.lazyfor all 7 heavy panels. Editor only pays its 4 MB cost when a file is actually opened.key:valuelines 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.src/lib/ai-pricing.ts).read_file,list_directory,get_directory_tree,search_code,web_search) nowPromise.allinstead of awaiting sequentially. Mutating batches stay sequential.:focus-visiblering system, WCAG AA light theme, 3-tier shadow scale,prefers-reduced-motionsupport.Reliability / safety
aiStore.messagespersistence capped at last 50,fileStoreno longer persists file contents (reload from disk). Prevents the 5–10 MB localStorage quota from silently dropping the whole store.read_filerejects > 50 MB files;grep_searchskips them;get_directory_treecaps at 10k nodes and short-circuits on symlinks.run_shellnowlog::info!-logs every invocation result with byte counts.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.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.0npm run build— clean, initial chunk 411 KB (down from 1770 KB)cargo clippy --all-targets -- -D warnings— cleancargo test --all-targets— 16/16 passing (added 2 file-size limit tests)Commits (squashed individually for review)
perf(bundle): split monaco/hljs/xterm and lazy-load main panelsfeat(design): focus ring system, AA light theme, 3-tier shadowsfix(agent): tool-permission polling timeout no longer leaks setTimeoutfix(stores): bound persisted state to keep localStorage in budgetchore(release): add version-sync guard wired into CIfix(rust): bound file reads, tree traversal, and grep file sizefeat(ai): surface per-message + cumulative token costperf(agent): parallel dispatch for read-only tools in agent loopfeat(serial): live data plotter — CSV/JSON/key:value auto-parseddocs: SOTA roadmap and Tier 2-4 specschore(release): bump version to v0.39.0🤖 Generated with Claude Code