Skip to content

Polish GUI chat and persistent settings - #12

Merged
TraderSamwise merged 6 commits into
masterfrom
feat/gui-polish
May 23, 2026
Merged

Polish GUI chat and persistent settings#12
TraderSamwise merged 6 commits into
masterfrom
feat/gui-polish

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented May 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • allow the local web app to call the dev daemon
  • send chat composer input on Enter while preserving Shift+Enter newlines
  • render parsed user/assistant transcript blocks in the chat UI
  • add persisted Jotai settings for global chat display preferences
  • make AGENTS.md the canonical shared agent instruction file with CLAUDE.md as an adapter

Verification

  • yarn --cwd app typecheck
  • yarn --cwd app test settings.test.ts parsed-transcript.test.ts MessageBlock.test.ts
  • yarn --cwd app lint (passes with existing warnings)
  • git push hook: yarn typecheck && yarn lint && yarn test (80 files, 606 tests)
  • browser reload preserved split mode via aimux-settings localStorage

Summary by CodeRabbit

  • New Features

    • Optional split "Live output" view for agent responses (web toggle)
    • Chat now prefers parsed agent messages and reconciles session routes
    • Press Enter to submit messages on web
    • New persistent "Chat" setting to toggle output split; theme defaults to dark
  • Documentation

    • Rewrote canonical agent/operator guides and updated Claude-specific operator notes
  • Bug Fixes

    • Enabled CORS for the local daemon to improve browser/mobile connectivity
  • Tests

    • Added tests for parsed-message conversion, settings persistence, and daemon CORS behavior

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5669d162-49d4-4904-b5b8-ca06083b10dd

📥 Commits

Reviewing files that changed from the base of the PR and between 041d3dd and 2ee2c64.

📒 Files selected for processing (1)
  • app/lib/parsed-transcript.test.ts

📝 Walkthrough

Walkthrough

Displays structured parsed agent output in chat (preferring parsed messages), adds a web-only configurable split terminal persisted in settings, enables Enter-to-send on web, and implements daemon CORS preflight handling with tests.

Changes

Parsed Agent Output & Split Terminal Feature

Layer / File(s) Summary
Agent & Operator Guidance
AGENTS.md, CLAUDE.md
Adds canonical agent instructions (AGENTS.md) and Claude-specific operator notes (CLAUDE.md).
Settings Persistence & Storage
app/lib/jotai-storage.ts, app/stores/settings.ts, app/stores/settings.test.ts, app/stores/ui.ts, app/lib/theme-effect.ts
Introduces createSsrSafeMergingJsonStorage and a persistent settings store exposing settingsAtom, themePreferenceAtom, and chatTerminalSplitAtom; moves theme persistence out of ui.ts; includes tests validating focused atoms and persistence.
Event Schema & Chat Store Updates
app/lib/events.ts, app/stores/chat.ts
Makes ParsedAgentOutput.blocks element fields optional and adds parsedOutputFamily atom family; ingestEventAtom now writes event.parsed into the session-scoped atom.
Parsed Transcript Conversion Utilities
app/lib/parsed-transcript.ts, app/lib/parsed-transcript.test.ts
Adds messagesFromParsedAgentOutput to convert parsed blocks (prompt/response) into ChatMessage arrays and pendingPromptAlreadyRendered to detect duplicate pending prompts; tests cover deterministic conversion and duplicate filtering and accept both type and kind block shapes.
Chat Screen Parsed Output & Split Terminal
app/app/(main)/agent/[sessionId]/chat.tsx
Reads parsedOutput from Jotai, derives parsedMessages, prefers parsed messages (with pending filtering) over history, adds route→project reconciliation, and renders an optional web-only split terminal pane driven by chatTerminalSplitAtom.
Settings Screen Chat Terminal Control
app/app/(main)/settings.tsx
Adds a "Chat" segmented control mapping chatTerminalSplitAtom boolean to "off"/"on" UI values and writing updates back to the atom.
Chat Composer Keyboard Submission
app/components/ChatComposer.tsx
Handles Enter (without Shift) on web to submit the composer via onKeyPress, wired into TextInput.
Daemon CORS & Preflight Handling
src/daemon.ts, src/daemon.test.ts
Adds CORS_ALLOWED_ORIGINS, setCorsHeaders()/rejectCors(), short-circuits OPTIONS preflight with 204, and adds tests verifying allowed preflight and rejection of disallowed origins.

Sequence Diagram(s)

sequenceDiagram
  participant Relay as Relay Polling
  participant ChatScreen
  participant Transcript as Parsed Transcript Lib
  participant MessageList as Message Renderer
  Relay->>ChatScreen: outputResult.parsed
  ChatScreen->>Transcript: messagesFromParsedAgentOutput(parsed)
  Transcript->>ChatScreen: ChatMessage[] (filtered)
  ChatScreen->>MessageList: render allMessages (parsed or merged)
  Note over ChatScreen: Chat terminal split toggles pane visibility
Loading
sequenceDiagram
  participant Browser
  participant Daemon
  Browser->>Daemon: OPTIONS /projects (preflight)
  Daemon->>Daemon: handle() detects OPTIONS
  Daemon->>Daemon: setCorsHeaders(res)
  Daemon->>Browser: 204 + CORS headers
  Browser->>Daemon: GET /projects (actual request)
  Daemon->>Daemon: setCorsHeaders(res)
  Daemon->>Browser: 200 + JSON + CORS headers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hop through parsed blocks with delight,

Split panes open left when the web asks for light,
Settings tucked safe so preferences stay true,
Enter sends words — the composer says who,
CORS waves hello so browsers may view.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Polish GUI chat and persistent settings' is directly related to the main changes—it accurately captures the core updates to chat UI rendering, composer input handling, and persistent settings management across the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gui-polish

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app/lib/parsed-transcript.test.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
app/lib/parsed-transcript.test.ts (1)

9-43: ⚡ Quick win

Add a regression case for kind-based parsed blocks.

These tests only exercise type, but messagesFromParsedAgentOutput also accepts kind. Add one case using kind: "prompt" | "response" to lock compatibility behavior.

Suggested test addition
+  it("accepts legacy kind-based prompt/response blocks", () => {
+    const messages = messagesFromParsedAgentOutput({
+      blocks: [
+        { kind: "prompt", text: "hi" },
+        { kind: "response", text: "hello" },
+      ],
+    });
+
+    expect(messages).toEqual([
+      { id: "parsed-0-prompt", role: "user", parts: [{ type: "text", text: "hi" }] },
+      { id: "parsed-1-response", role: "assistant", parts: [{ type: "text", text: "hello" }] },
+    ]);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/lib/parsed-transcript.test.ts` around lines 9 - 43, The test currently
covers parsed blocks using the "type" property but misses blocks using the
alternative "kind" property; add a regression case in the same test file that
calls messagesFromParsedAgentOutput with blocks using kind: "prompt" and kind:
"response" (instead of type) and assert the returned messages match the same
ordered user/assistant message shape and ids as the existing assertions; ensure
you reference messagesFromParsedAgentOutput and include mixed cases (e.g., some
blocks using kind and some using type) if you want to lock compatibility
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/app/`(main)/agent/[sessionId]/chat.tsx:
- Around line 246-259: The split-toggle UI (the View containing the Pressable
that uses setShowTerminalSplit, canShowTerminalSplit and showSplit) is being
rendered on native where it is always disabled; wrap or conditionally render
that entire control so it only appears on web (e.g., check Platform.OS === 'web'
or use a provided isWeb helper) and hide it on non-web so the inert header
action is not shown.

In `@app/stores/settings.test.ts`:
- Around line 25-32: The test setup stubs the global window in beforeAll via
vi.stubGlobal("window", globalThis) but never restores it; add a teardown to
call vi.unstubAllGlobals() (use afterAll to match repo patterns) so subsequent
tests do not inherit the browser-like window; place the afterAll alongside
beforeAll in the same test file (settings.test.ts) to ensure cleanup after
tests, referencing the existing beforeAll and vi.stubGlobal usage.
- Around line 54-66: The test uses a fixed sleep (setTimeout) which is flaky;
replace the sleep in the "persists focused atom writes to aimux-settings" test
by polling until persistence appears: after calling createStore() and
store.set(settingsModule.themePreferenceAtom, "light") /
store.set(settingsModule.chatTerminalSplitAtom, true), use a testing utility
like Vitest's waitFor to repeatedly call AsyncStorage.getItem("aimux-settings")
and assert that JSON.parse(...) equals the expected object, instead of awaiting
setTimeout(50); this ensures the assertion waits for the persistence to complete
reliably.

In `@src/daemon.ts`:
- Around line 163-167: Replace the wildcard CORS in setCorsHeaders with an
explicit allowlist check: read the incoming Origin header, compare it against a
whitelist (e.g., http://localhost, http://127.0.0.1 and any required dev
origins), and only set Access-Control-Allow-Origin and related headers when
Origin is allowed; for disallowed Origins do not set the CORS headers and ensure
preflight OPTIONS requests are rejected with a 403 (and actual requests receive
a 403/unauthorized response). Update any other CORS helper near the daemon
request handling (the other block referenced in the diff) to use the same Origin
allowlist check so both preflight and normal requests consistently reject
non-allowed origins.

---

Nitpick comments:
In `@app/lib/parsed-transcript.test.ts`:
- Around line 9-43: The test currently covers parsed blocks using the "type"
property but misses blocks using the alternative "kind" property; add a
regression case in the same test file that calls messagesFromParsedAgentOutput
with blocks using kind: "prompt" and kind: "response" (instead of type) and
assert the returned messages match the same ordered user/assistant message shape
and ids as the existing assertions; ensure you reference
messagesFromParsedAgentOutput and include mixed cases (e.g., some blocks using
kind and some using type) if you want to lock compatibility behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b72aca72-5402-4e1a-ae08-4fc79e40b37a

📥 Commits

Reviewing files that changed from the base of the PR and between 69929c3 and 69756ed.

📒 Files selected for processing (16)
  • AGENTS.md
  • CLAUDE.md
  • app/app/(main)/agent/[sessionId]/chat.tsx
  • app/app/(main)/settings.tsx
  • app/components/ChatComposer.tsx
  • app/lib/events.ts
  • app/lib/jotai-storage.ts
  • app/lib/parsed-transcript.test.ts
  • app/lib/parsed-transcript.ts
  • app/lib/theme-effect.ts
  • app/stores/chat.ts
  • app/stores/settings.test.ts
  • app/stores/settings.ts
  • app/stores/ui.ts
  • src/daemon.test.ts
  • src/daemon.ts
💤 Files with no reviewable changes (1)
  • app/stores/ui.ts

Comment thread app/app/(main)/agent/[sessionId]/chat.tsx
Comment thread app/stores/settings.test.ts
Comment thread app/stores/settings.test.ts
Comment thread src/daemon.ts Outdated

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/daemon.test.ts (1)

427-428: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid fixed daemon ports in integration tests to prevent flaky failures.

Line 427 and Line 458 hard-code ports (49191, 49192). If either port is occupied, these tests fail intermittently.

💡 Suggested change
+import { createServer } from "node:net";
+
+async function getFreeLoopbackPort(): Promise<number> {
+  return await new Promise<number>((resolve, reject) => {
+    const s = createServer();
+    s.once("error", reject);
+    s.listen(0, "127.0.0.1", () => {
+      const addr = s.address();
+      if (!addr || typeof addr === "string") {
+        s.close();
+        reject(new Error("failed to allocate loopback port"));
+        return;
+      }
+      const port = addr.port;
+      s.close((err) => (err ? reject(err) : resolve(port)));
+    });
+  });
+}
...
-    const port = "49191";
+    const port = String(await getFreeLoopbackPort());
...
-    const port = "49192";
+    const port = String(await getFreeLoopbackPort());

Also applies to: 458-459

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/daemon.test.ts` around lines 427 - 428, Replace the hard-coded port
assignments in the test (the local variable named port and the env var
process.env.AIMUX_DAEMON_PORT used in src/daemon.test.ts) with a dynamic free
port allocation (for example: obtain an available port via the OS by binding to
port 0 or using a helper like get-port) and set process.env.AIMUX_DAEMON_PORT to
that value; do the same for the second occurrence around the other test (the
block that currently uses "49192") so both tests use unique ephemeral ports and
avoid collisions, and ensure any helper used returns the numeric port as a
string if needed and is cleaned up after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/daemon.test.ts`:
- Around line 427-428: Replace the hard-coded port assignments in the test (the
local variable named port and the env var process.env.AIMUX_DAEMON_PORT used in
src/daemon.test.ts) with a dynamic free port allocation (for example: obtain an
available port via the OS by binding to port 0 or using a helper like get-port)
and set process.env.AIMUX_DAEMON_PORT to that value; do the same for the second
occurrence around the other test (the block that currently uses "49192") so both
tests use unique ephemeral ports and avoid collisions, and ensure any helper
used returns the numeric port as a string if needed and is cleaned up after the
test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: df52ae6e-3706-4fb4-b2d6-1f46cc6ef192

📥 Commits

Reviewing files that changed from the base of the PR and between 69756ed and 041d3dd.

📒 Files selected for processing (4)
  • app/app/(main)/agent/[sessionId]/chat.tsx
  • app/stores/settings.test.ts
  • src/daemon.test.ts
  • src/daemon.ts

@TraderSamwise
TraderSamwise merged commit 56a7b75 into master May 23, 2026
1 check passed
@TraderSamwise
TraderSamwise deleted the feat/gui-polish branch May 23, 2026 10:41
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.

1 participant