At Windows display scaling above 100% (125%/150%, common on laptops), fonts
grew with the scale but hardcoded pixel sizes did not: the Log/Script/Stop
buttons on each session were truncated, the AI Chat toolbar button lost its
bottom edge at 150%, and absolute-coordinate dialogs (Quick Connect / New
Connection) could clip or overlap their fields.
Root cause: MainForm's AutoScaleMode.Dpi scales only the controls that exist
when the form initializes. Almost all of this app's UI is created at runtime
(SessionPage on open, views built in constructors after InitializeComponent),
so nothing was ever scaled — verified at 125%: the 250px sidebar stayed 250px.
Fix, in order of preference:
- New Theme.Dpi(Control, px) extension (px * DeviceDpi/96, same approach as
the existing ActivityRail.ItemSize) applied to every hardcoded dimension:
sidebar, toolbars, tab strips, Settings inputs and grids, PDU status grid,
AI chat pane, Ctrl+F search bar, DarkScrollBar width.
- Buttons whose text changes at runtime (Log <-> Logging, Log All <->
Logging All) now size from measured text width (SessionPage.BarButtonWidth)
or plain AutoSize, so they can never truncate at any scale.
- Multi-line instruction labels switch to AutoSize instead of fixed frames.
- Absolute-coordinate dialogs get DarkDialog.ApplyDpiScale(): one recursive
Control.Scale(DeviceDpi/96) at the end of each constructor.
- MainForm moves to AutoScaleMode.None with ClientSize/MinimumSize scaled
manually — we own scaling explicitly, so a future .NET change to auto-scale
semantics cannot double-scale us.
At 100% the conversion is the identity, so existing setups are unchanged.
Known limitation: changing the system scale while running needs an app
restart (no WM_DPICHANGED re-layout).
Also rides along in AboutView: the v0.7.2 changelog entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKF7C75bhA4ArQhyMpnsxU
The About page changelog is a hardcoded array, so it did not list v0.7.1
even though the title bar and About header already read 0.7.1 (those come
from the assembly version).
Also rework docs/release-notes/v0.7.1.md to the house format: New features
before Bug fixes, bold group headings, and the standard Downloads table.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKF7C75bhA4ArQhyMpnsxU
A device that is still booting can silently discard console input the moment its
shell takes over the tty (tty reopen / termios flush). The line sent by `sendln`
vanishes — the device neither echoes nor runs it — so the following `wait` blocks
forever and the rig sits dead.
This is a race, not a delay: `pause` before `sendln` only lowers the odds of hitting
the window, it can never close it. Evidence from a 45-cycle overnight power-cycle run
(For_AI/[COM121]_20260715): 3 cycles hung this way, and the swallowed sends landed at
the same DUT uptime (31.8-34.8s) as the 42 that worked. The `random: crng init done`
line those hangs share is a symptom, not the cause — it only appears because a hung
script stops power-cycling, letting the DUT reach uptime 89.7s it never otherwise sees.
Single-string `wait` aborts the script on timeout (deliberate, see CLAUDE.md), so the
TeraTerm idiom `wait` -> `if result = 0 then goto retry` cannot express a resend here.
Hence a new command rather than a semantics change:
sendlnretry '<text>' '<confirm keyword>' [max attempts]
Sends, then waits for proof the device actually ran it, and resends if that proof does
not arrive. Attempts omitted = retry until it gets through. On success result=1; when
attempts run out result=0 and the script continues so it can handle the failure.
Semantics: clears the receive buffer before each send (a match can only come from this
send); on a hit consumes only up to the first occurrence, leaving the rest for the next
`wait`; deliberately no settle (the keyword appearing is itself proof). Per-attempt
timeout follows timeout/mtimeout when set, else 3s — unlike `wait`, 0 cannot mean
"forever" here since that would mean never retrying.
Not yet run against hardware: compiles clean (cross-built win-x64 on Linux), but the
DUT power-cycle rig is the first real execution.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKF7C75bhA4ArQhyMpnsxU
The AI Assistant pane moves to WebView2: real chat bubbles (user right, AI
left), full Markdown, and an animated thinking indicator while the AI works.
The tool-call limit is now a Settings value (0 = unlimited) with a Stop button
in the chat to abort long/automation runs. Verified end-to-end against an
Arduino serial DUT.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the fixed 30-round cap with a Settings value (AiMaxToolRounds,
Settings → AI Assistant). 0 = unlimited, for long automation runs left
going for hours. The chat's Send button turns into Stop while the agent
is running so any run — bounded or unlimited — can be aborted (via the
CancellationToken; the loop also checks it each round).
Docs: ARCHITECTURE gains the configurable-limit note and a generic
"pair with a self-hosted gateway = hardware assistant" usage section
(no private endpoints).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Multi-step hardware tasks (operate several ports, repeated read/write,
power-cycle waits) hit the old 8-round cap easily — e.g. "send help 3
times" needs 8 calls and stopped short of the final summary. 30 keeps
the runaway-loop guard while allowing normal workflows to finish.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The AI Chat pane's message area moves from RichTextBox to WebView2:
- Real chat bubbles (user right, AI left) with full Markdown via Markdig
(code blocks, tables, lists, inline code).
- Sending a prompt shows an animated "…" thinking bubble that clears when
the reply lands (AgentHost Status "thinking" → showThinking; AssistantText
→ hideThinking + bubble).
- HTML/CSS/JS template inlined in Ai/ChatHtml.cs (NavigateToString, no
external deps); C# drives it via ExecuteScriptAsync. Calls made before
WebView2 is ready are queued and flushed on NavigationCompleted. User-data
folder under %LocalAppData%\ETTerms\WebView2.
Bottom bar (input / model dropdown / Send), [AI] serial tagging and PDU
confirmations are unchanged. Adds Microsoft.Web.WebView2 + Markdig; needs
the WebView2 Runtime (built into Windows 11). Publish verified to include
the native WebView2Loader.dll.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drive serial & PDU in plain language from a workspace pane you can tile
next to a live terminal. BYO OpenAI-compatible endpoint (nothing baked
in), model dropdown in the pane, destructive PDU actions gated by a
confirmation, API key in Credential Manager. Separate from the existing
Serial/PDU MCP servers for external AI CLIs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Settle the AI chat pane on a clean, terminal-consistent layout instead of
faux bubbles in a RichTextBox:
- Messages: user right-aligned accent, AI left, tool activity quiet gray,
"thinking" folded into the Send button (no transcript spam).
- Bottom bar: input (fill) + bottom-right column (model dropdown over Send).
- Model dropdown lists the endpoint's /v1/models, switches on the fly, and
is remembered — Settings → AI Assistant now only holds Base URL / API Key
/ system prompt (configured = Base URL set). OpenAiChatClient.Model is
mutable + ListModelsAsync.
Real chat bubbles + Markdown via WebView2 are logged as a v0.7.0 option in
ARCHITECTURE Future Extensions rather than hacked onto RichTextBox.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Chat UI now Claude-style: user messages right-aligned with an accent
bubble (no "你:" prefix), AI replies left-aligned.
- Model dropdown at the bottom, populated from the endpoint's GET
/v1/models, lets you switch models without opening Settings; the choice
is remembered. OpenAiChatClient.Model is now mutable + ListModelsAsync.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Open it from the toolbar (✨ AI Chat) and lay it out next to a Serial
session with Layout (1×2, 2×2…) — chat on one side, watch the terminal
on the other, like Claude Code / Kiro. Previously it was a full-page
Activity Rail view that couldn't sit beside a live session.
WorkspaceView.Session is now abstracted to hold either a SessionPage or
an AiChatView via a Content property; group/log/script actions skip AI
panes. Removed the standalone Ai rail view; Settings → AI Assistant
(provider config) is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a GUI AI Assistant view (✨ rail) that drives serial + PDU in plain
language, without depending on Claude/Kiro. In-process function calling
(no MCP hop): Ai/OpenAiChatClient (minimal OpenAI-compatible client) +
Ai/AgentHost (hand-written agent loop) + Ai/AiTools (serial via the
existing SerialBridge with [AI] echo; PDU via ETTerms.PduCore).
BYO endpoint: Base URL / Model / API Key set in Settings → AI Assistant,
blank by default = disabled. No private endpoint ships in the app; API
key lives in Windows Credential Manager, never in settings.json or code.
Safety: destructive PDU actions (outlet off / power-cycle) require a GUI
confirmation; every tool call is written to AppLogger. Existing Serial/
PDU MCP servers (Settings → AI MCP) are unaffected and keep serving
external AI CLIs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Settings -> Terminal gains a 'Show script trace in terminal' checkbox
(AppSettings.ShowScriptTrace, default on). When off, TTL trace lines
([wait] progress, sent-command echo, errors) are no longer drawn in
the terminal. dispstr moves to a dedicated Display event and always
shows, since it is the script explicitly printing something.
Trace was already display-only (never written to session logs, the AI
serial bridge, or the device) - docs now state this explicitly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Terminal:
- cache font variants + single reusable brush in render hot path;
resolve cell colors once per cell; dispose GDI resources
- scrollback: List+RemoveRange -> O(1) ring buffer
- follow new output only when already at bottom (no more yank-to-bottom
while reading history)
- alt-screen mouse wheel -> arrow keys (vim/htop scroll)
- answer DSR (ESC[5n/6n) and DA (ESC[c) queries so TUIs no longer hang
- remove dead code in OnKeyPress
Encoding correctness (garbled CJK across chunk boundaries):
- stateful UTF-8 Decoder in TTLInterpreter.OnData, SerialBridgeServer
rx forwarding, and SessionLogger.Write
Sessions:
- ShellChannel: free proc-thread attribute list, close hProcess,
notify '[ETTerms] shell process exited' in the tab
- SshChannel: surface ErrorOccurred / ShellStream.Closed in the tab;
Write no longer throws into the UI thread on a dead connection
PDU:
- new shared ETTerms.PduCore project replaces the two drifted copies of
PduController (GUI + PduMcp); logging via injected delegates
- batched SNMP GET (GetAllPortsStatus): 12-port poll is 3 UDP
round-trips instead of 36 (StatusView polling + pdu_status tool)
Scripting:
- cap TTL receive buffer at 1MB; skip re-scan in wait when buffer
length unchanged
- merge ScriptRunner.RunAsync/RunGroupAsync; new TtlScript helper
dedups script picking + group-command checks (3 copies -> 1)
Misc:
- ConnectionStore: parse LastUsedUtc with InvariantCulture/RoundtripKind
- version 0.4.0; About changelog; CLAUDE.md notes (intentional group
barrier behavior, SSH.NET reflection resize caveat)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump the version badge to 0.3.3 and add the v0.3.3 entry to Version History
in both README.md and README.zh-TW.md (terminal input fix). Add the v0.3.3
release note, and include the previously uncommitted v0.3.2 release note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The terminal's DarkScrollBar derives from Control, which is selectable by
default. Once enough output accumulated to create scrollback, the scrollbar
became enabled and was the only selectable child of the TerminalView
(a ContainerControl). On window re-activation (minimize/restore, click away
and back) WinForms restored focus down the container chain onto the scrollbar,
which has no keyboard handling, so typing/Enter went nowhere until the session
was reopened. This affected both Serial and PowerShell tabs (shared TerminalView),
and only after output appeared — matching the reported symptom.
Make the scrollbar mouse-only: SetStyle(Selectable, false) + TabStop=false, so
it never takes keyboard focus and the TerminalView keeps focus itself, exactly
as it already did when idle (scrollbar disabled).
Bump version to 0.3.3 and add the About changelog entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New left-rail Status view (StatusView.cs) hosting the PDU tab; PDU panel moved out of SettingsView and auto-polls all 12 outlets every 3s on a background thread (no manual Refresh).
- Each outlet row gets a Control button (DataGridViewButtonColumn) that toggles the port via SNMP off the UI thread, re-reads after ~400ms, and resets the grid on disconnect.
- fix(shell): ShellChannel falls back to the user home folder when StartupDirectory no longer exists (avoids CreateProcess 267).
- docs: update ARCHITECTURE / CLAUDE / README / README.zh-TW and About changelog; bump version to 0.3.2.
- Add owner-drawn DarkScrollBar (slim, dark, rounded thumb) to TerminalView; content width excludes the bar so text isn't covered; synced on feed/wheel/resize.
- Support bracketed paste (DEC mode 2004): wrap multi-line paste in ESC[200~..ESC[201~ when the app enables it (PSReadLine/Kiro CLI), so pasted lines aren't each submitted immediately.
- Clear text selection after right-click copy so the user knows it worked.
- Bump version to 0.3.1; rewrite v0.3.0 changelog in plain language and add v0.3.1 entry; update ARCHITECTURE.md / CLAUDE.md.
New ETTerms.PduMcp stdio MCP server lets AI agents (Kiro/Claude CLI)
control SNMP PDU outlets directly. Unlike serial, SNMP is non-exclusive
so it talks to the PDU directly without bridging through the GUI.
Tools: pdu_connect / pdu_list / pdu_set_port / pdu_get_port / pdu_status
/ pdu_power_cycle / pdu_disconnect.
- McpRegistrar now registers both etterms-serial and etterms-pdu in one click
- Settings -> AI MCP shows both server paths
- publish target (PublishMcpServers) bundles both MCP servers
- bump version to 0.3.0, update About changelog, ARCHITECTURE.md, CLAUDE.md
Switch to AutoScaleMode.Dpi (PerMonitorV2) so control sizes scale by the exact DPI ratio, matching point-font rendering and eliminating text/button clipping at 125%/150%. Make toolbar, settings, sidebar buttons and About labels AutoSize; align left tab bar with workspace toolbar (both 40px); About cards now auto-grow and the left column scrolls. ActivityRail cell size is DPI-aware. No version bump.
OnSizeChanged now ignores degenerate sizes (minimized / smaller than one cell), so ConPTY no longer receives a 1x1 size that collapsed full-screen TUIs like Kiro CLI. ScreenBuffer.Resize reflows both the live and saved alt-screen buffers instead of discarding the main-screen backup. TerminalInput.Map sends LF for Shift+Enter (CR for plain Enter) so the shell inserts a newline for multi-line input. Bump version to 0.2.2 and add About changelog entry.
PublishSerialMcp target now inherits the GUI's SelfContained setting, so a --self-contained true publish also produces a runtime-free ETTerms.SerialMcp. Document both publish variants in ARCHITECTURE.md / CLAUDE.md: framework-dependent ETTerms_v{Version}\ and portable ETTerms_v{Version}_portable\ (no .NET runtime install needed).
Add Settings -> AI MCP tab with McpRegistrar to one-click register/remove the Serial MCP server into Claude Code (~/.claude.json) and Kiro (~/.kiro/settings/mcp.json); read-modify-write preserves other servers, atomic write-back. Each card shows a CLI verify command.
Add PublishSerialMcp MSBuild target so a single GUI publish auto-bundles ETTerms.SerialMcp into the \ETTerms.SerialMcp\ subfolder, aligning with McpRegistrar.ResolveServerExe(). Bump version to 0.2.1. Update ARCHITECTURE/CLAUDE/README/README.zh-TW and About changelog.
- About: add v0.2.0 "Beta Version Release" changelog entry.
- MainForm: title bar now shows "ETTerms vX.Y.Z" (drop the word "Version").
- ARCHITECTURE.md + CLAUDE.md: document the publish convention --
output to src\ETTerms\Publish\ETTerms_v{Version}\, main exe renamed to
"ETTerms v{Version}.exe", ETTerms.SerialMcp published into a subfolder.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 9 (AI integration) complete -- AI agents can send/receive serial
while the user watches live in the GUI, with the GUI as sole COM-port owner.
- ETTerms.SerialMcp: stdio MCP server (net8.0 + ModelContextProtocol SDK)
exposing serial_list / serial_attach / serial_write / serial_read
(waitFor + timeoutMs) / serial_detach; forwards over named pipe.
- GUI SerialBridgeServer (pipe etterms-serial) + SerialBridge endpoint;
SessionPage.WriteFromAi echoes MCP-sourced TX tagged [AI] (magenta).
- App icon: window title bar / taskbar / exe now use Choco_256x256.ico
(embedded via AppAssets); title bar shows ETTerms Version vX.Y.Z.
- About page: large Choco icon (Zoom, no crop).
- Docs: ARCHITECTURE.md + CLAUDE.md mark Phase 9 done; docs/serial-mcp-guide.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GUI holds the COM port exclusively; ETTerms.SerialMcp becomes a thin client that bridges over a local named pipe (SerialBridgeServer). AI TX/RX flows through the GUI channel so it is visible live in the terminal view. Tools change from open/close to attach/detach.