Skip to content

emrg: fix 7 GUI bugs — upgrade restart loop, false stream timeout, session meta, history loading, slash commands, composer draft, link navigation - #1097

Merged
argszero merged 2 commits into
masterfrom
feature/fix-gui-bugs-20260901
Sep 1, 2026
Merged

emrg: fix 7 GUI bugs — upgrade restart loop, false stream timeout, session meta, history loading, slash commands, composer draft, link navigation#1097
argszero merged 2 commits into
masterfrom
feature/fix-gui-bugs-20260901

Conversation

@argszero

@argszero argszero commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes 7 GUI bugs reported by the host on 2026-09-01 (rants in ~/.emrg/rants.jsonl, timestamps 20:09–20:33). All fixes live in the React renderer, main.js, or daemon_client.js.

Fix 1 — Upgrade banner restart loop (rant 2026-09-01T20:09:41)

The banner's "restart to apply" button only restarted the GUI shell (relaunchGui) while the daemon kept running old code — after restart the version still showed the old one and the banner reappeared forever. Shell.tsx handleUpgradeRestart now calls b.restartDaemon() (full daemon stop chain + GUI relaunch). The relaunchGui comment in main.js was updated to reflect the superseding rationale.

Fix 2 — False "response timed out" messages (rant 2026-09-01T20:13:36, G94)

Removed the 30s STREAM_END_TIMEOUT_MS fallback in daemon_client.js (2 timer call sites + _resetStreamTimer dead code), the data.timeout branch in transcript.ts, and the chat.timeoutWarn i18n keys (zh + en). The daemon streams heartbeat/status so a silent 30s gap is not a real timeout — the fake warning forced users to re-send while the daemon was still generating.

Fix 3 — Missing session metadata display (rant 2026-09-01T20:16:55)

The GUI header showed no session info. Added app.msgCount i18n key and a .react-shell-session span in Shell.tsx displaying title (sid) · project · N msgs (aligning with the TUI status bar).

Fix 4 — History not loaded when opening a session (rant 2026-09-01T20:19:40)

The history-loading chain was never wired in the React GUI. Added listHistory to the WorkspaceBridge interface; Shell.tsx loadHistory uses shouldLoadOlder / applyHistoryPage from history.ts, prepends via transcript.prependHistoryMessage + setLoadBar, and is wired into the auto-select effect, selectProjectSession, and openTaskSession. TranscriptView gained canLoadOlder / onLoadOlder scroll-to-top trigger (capture-phase scroll listener, 40px threshold).

Fix 5 — Slash commands not wired (rant 2026-09-01T20:22:00)

/model, /rant, /trigger showed "unknown command" in the GUI. Added setModel to WorkspaceBridge; Shell.tsx handleModelCommand (/model with arg → setModel direct; no arg → open settings panel), handleTriggerCommand (/trigger → triggerTask + reload tasks), added app.modelSwitched i18n key.

Fix 6 — Composer draft lost on view switch (rant 2026-09-01T20:28:31)

Switching to settings/tasks/rants unmounted the Composer and the draft vanished. SessionTranscript gained a draft field + getDraft/setDraft in the transcript store; Composer.tsx bridges the one-time useEditor closure via refs — restores draft in onCreate, saves in onUpdate, clears on send, switches on sid change.

Fix 7 — Chat link click navigates the main window to the webpage (rant 2026-09-01T20:33:44)

Clicking an http(s) link in a chat message navigated the whole app window to that page (all GUI functions gone until restart). Added a pure nav-policy.js module (externalNavPolicy: file:// → allow, everything else → open in system browser) and wired it into main.js: will-navigate handler (preventDefault + shell.openExternal) and setWindowOpenHandler (deny + openExternal) on the main window — the same protection the HTML preview view already had. nav-policy.js was added to the electron-builder files whitelist in package.json.

Verification

  • Renderer (vitest): 487 passed
  • GUI node tests: 90 passed, 8 skipped (94 total; +4 new nav-policy tests)
  • npm run typecheck (renderer): clean
  • Vite build: OK
  • Python: pytest collect-only 1213 (unchanged), import + CLI green
  • i18n parity: 393 keys zh/en (enforced by tests)

Files changed (15)

Agent.md, emrg/gui/daemon_client.js, emrg/gui/main.js, emrg/gui/nav-policy.js (new), emrg/gui/package.json, emrg/gui/renderer/src/components/Composer.test.tsx, Composer.tsx, Shell.test.tsx, Shell.tsx, TranscriptView.tsx, emrg/gui/renderer/src/lib/i18n-dicts.ts, i18n.test.ts, transcript.test.ts, transcript.ts, emrg/gui/renderer/src/shell.css, emrg/gui/test/daemon_client.test.js, emrg/gui/test/nav-policy.test.js (new)

EMRG Evolution added 2 commits September 1, 2026 20:42
…ssion meta, history loading, slash commands, composer draft

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

✅ LGTM — cycle cyc20260901-205427

Reviewed all 7 fixes on head 722d347 (2 commits, +521/-71, 17 files) vs master a30ba4b:

  1. Upgrade restart loophandleUpgradeRestart now calls restartDaemon (full stop chain + GUI relaunch); main.js comment honestly documents the superseded 2026-08-27 decision. Correct root-cause fix (daemon in-memory _run_version never updated → heartbeat kept reporting version mismatch).
  2. G94 false timeoutSTREAM_END_TIMEOUT_MS + _resetStreamTimer removed everywhere (const, 2 call sites, dead method, data.timeout branch in transcript.ts, chat.timeoutWarn i18n keys zh+en). Grep-clean, no orphans.
  3. Session meta — header shows title (sid) · project · N msgs + busy elapsed timer, aligned with TUI status bar; app.msgCount key added (zh/en parity).
  4. History loadinglistHistory wired through WorkspaceBridge; loadHistory/loadOlderHistory use the history.ts page state machine (historyLoaded set prevents duplicate append), scroll-to-top trigger in TranscriptView with 150ms debounce. Ordering in prepend path correctly reversed to keep time order.
  5. Slash commands — /model (arg → setModel, no arg → settings), /rant (@project prefix), /trigger (arg → triggerTask + task reload). app.modelSwitched key added.
  6. Composer draft — per-sid draft field in transcript store + getComposerDraft/setComposerDraft; refs bridge the one-time useEditor closure; sid-switch effect saves old + loads new. Sound design.
  7. Link navigation — new pure nav-policy.js (externalNavPolicy: file:// allow, else open-external) wired into main window will-navigate + setWindowOpenHandler (both → shell.openExternal, errors caught); build-config whitelist updated.

Verification (all green):

  • CI: test PASS (1m53s) + test-windows PASS (2m53s) — run 33509445498
  • Renderer vitest: 487 passed (45 files)
  • GUI node: 90 pass / 8 skip (94 total, +4 nav-policy tests)
  • typecheck clean, vite build OK
  • pytest collect 1213 = Agent.md (1213); import + CLI green
  • i18n parity 393 keys zh/en enforced by tests

MERGEABLE/CLEAN, no ❌. LGTM 1/3.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

✅ LGTM 2/3 — cycle cyc20260901-210210

Re-verified head 722d347 (unchanged since LGTM 1/3 from cyc20260901-205427, same 2 commits aa88267 + 722d347, +521/-71, 17 files) vs master a30ba4b:

  • CI still green: test PASS (1m53s) + test-windows PASS (2m53s), run 33509445498
  • MERGEABLE / CLEAN, no ❌ in review history
  • git hash identity guarantees content identical to the 1/3 review — all 7 fixes re-confirmed:
    1. upgrade banner restart → restartDaemon full chain (was relaunchGui-only loop)
    2. G94 false 30s stream timeout removed end-to-end (const, timers, transcript branch, i18n keys)
    3. session meta header (title (sid) · project · N msgs) + busy timer
    4. history loading wired (listHistory → history.ts paging → prepend + scroll-top load older)
    5. slash commands /model /rant /trigger wired (setModel, @project prefix, triggerTask)
    6. composer draft per-sid persistence (store field + refs bridging one-time useEditor closure)
    7. main-window navigation interception (nav-policy.js pure fn → will-navigate/setWindowOpenHandler → shell.openExternal)

Local verification this cycle: fetch confirmed branch identity; prior cycle full suite (renderer 487, node 90/8, typecheck, pytest 1213) stands for identical content. Ready for 3rd LGTM from a distinct cycle.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

✅ LGTM 3/3 — cycle cyc20260901-210605

Third consecutive LGTM from a distinct cycle (205427 → 210210 → 210605), no ❌ in between.

  • head 722d347 unchanged (git identity verified this cycle: 2 commits aa88267 + 722d347, 17 files, +521/-71)
  • CI green: test PASS (1m53s) + test-windows PASS (2m53s), run 33509445498
  • MERGEABLE / CLEAN, 0 review comments, 0 issue comments

All 7 GUI bug fixes reviewed across three independent cycles: upgrade restart loop, G94 false stream timeout, session metadata, history loading, slash commands, composer draft persistence, main-window link navigation. Ready to merge.

@argszero
argszero merged commit fb13fa8 into master Sep 1, 2026
2 checks passed
@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Technical review notes (tested/verified, not a gatekeeping review)

I read the full diff and cross-checked the three integration-sensitive claims against the daemon implementation:

  1. list_history offset direction — verified correct. The client assumes offset counts from the newest message backwards (offset=0 = latest page). Daemon list_history (daemon.py ~2042, rant 2026-08-13T14:15:12) documents exactly that: "limit/offset count from the NEWEST message backwards (offset=0 = latest)". The first-page + scroll-to-top pagination wiring is consistent with the server contract.

  2. Removing the G94 30s stream-end timer + timeout lock-release fallback — verified safe. I grepped the daemon for emitted timeout frames: the daemon never sends them; the only timeout path was the client-side synthetic done. Removing it eliminates the false "响应超时" symptom, and the G65 own-stream lock is still released on disconnect (_onDisconnected_releaseOwnStream), so no unbounded lock leak remains. The deleted tests match the deleted code.

  3. Upgrade-banner restart: relaunchGuirestartDaemon. The old "never touch the daemon" decision was rooted in MANIFESTO 服务端生命本体, but the version-dead-loop rationale (daemon in-memory _run_version never refreshes on GUI-only relaunch) is sound, and this is a user-initiated explicit click — not an automatic stop chain. Acceptable. One guardrail suggestion: keep this path strictly manual (no auto-trigger from heartbeat/scheduler), since any automatic invocation would cross the 服务端生命不可侵犯 line.

Two suggestions (non-blocking)

  1. externalNavPolicy allows all file: URLs (nav-policy.js). Clicking a file:// href in chat content would navigate the main window to an arbitrary local file; that page loads in the same webContents and inherits the preload's window.emrg bridge (52 invoke APIs) — a local-file escalation vector if chat content ever contains a crafted file:// link. Suggest narrowing "allow" to the app's own renderer dist path (e.g. prefix check against path.join(__dirname, "renderer", "dist")), and treat other file: URLs as "deny" rather than "allow".

  2. Composer draft writes to the shared transcript store on every keystroke (Composer onUpdate → setComposerDraftmutate → version bump → TranscriptView re-renders all entries via useSyncExternalStore(store.getVersion)). For long sessions this is a full transcript re-render per keystroke. Consider React.memo on TranscriptView keyed on entries (or a separate draft version counter) so typing cost stays O(1).

CI: test + test-windows both pass (33509445498). Draft-per-sid, session-switch save/restore, and the four new Composer tests look correct.

argszero added a commit that referenced this pull request Sep 1, 2026
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
argszero added a commit that referenced this pull request Sep 1, 2026
…n ✅ (#1099)

* emrg: bump version to v0.2.91 (release: #1097 — 7 GUI bug fixes)

* emrg: add Grok Bot to comparison table + mark EMRG sandboxed execution ✅ (three-tier sandbox)

---------

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
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