Release v0.4.0 - #59
Merged
Merged
Conversation
- effective head calculation: prefer origin if ahead > local, otherwise pick the most-ahead remote among non-origin remotes - aheadCount via git rev-list --count local..effective - branchExists check for develop branch detection - CheckDiverged returns DivergedResult map per upstream (main/develop)
- register GET /api/worktrees/diverged (batch) and GET /api/worktree/diverged (single) - handleWorktreesDiverged: iterate all worktrees, call CheckDiverged per branch - handleWorktreeDiverged: resolve worktree path by id, reuse status handler logic - main worktree always gets empty DivergedResult (isMain branch returns early) - CurrentBranch failure returns 500 instead of silent empty result
feat: add branch divergence detection with diverge badge and API handlers
Render a small GitHub Mark icon to the right of the sidebar main workspace project name when the main repo's git remote resolves to github.com. Clicking opens the canonical https://github.com/<owner>/<repo> URL in a new tab via <a target="_blank" rel="noopener noreferrer">, with event.stopPropagation() so the icon click does not trigger the row's selectWorktree handler. Backend: - Add gitx.GitHubURL(root) resolver. Prefers 'origin', then iterates 'git remote'. Normalizes SCP / HTTPS / ssh:// forms, strips .git and trailing slashes, and returns '' for any host other than github.com (case-insensitive). GitHub Enterprise and non-Git remotes are intentionally not surfaced. - /api/main response gains a github_url string field (always present; empty when no link can be resolved). Tests cover all three URL formats, malformed inputs, GitHub Enterprise, non-GitHub hosts, .git suffix, and the /api/main field plumbing. Docs (per docs-first convention): CHANGELOG, API, ARCHITECTURE, PRD, README.md, README.zh-CN.md.
The githubSCPRE regex hardcoded 'git@github.com:...' as the only recognized SCP-style prefix. Repositories whose origin URL uses a non-'git' username (e.g. a '\~/.ssh/config' alias mapping 'github.com' to 'User mywork', or a CI bot configured as 'ci-bot@github.com:...') would silently fail to surface a GitHub icon in the sidebar — the URL was still valid, but the resolver returned '' so the icon was never rendered. Loosen the regex to '[^@/]+@github\.com:...' so it accepts any non-empty user segment, mirroring the existing githubSSHRE pattern and bringing the two ssh-shaped forms back into symmetry. - remote.go: relax githubSCPRE; update Go doc to mention arbitrary user and replace the misleading 'default SSH port only' line. - gitx_test.go: add three new parse cases (ssh config alias user, CI bot user, dotted user) so the relaxation is covered. - docs/API.md: update the supported URL formats list to call out the arbitrary user segment. No behavior change for existing 'git@' URLs; tests + vet + build all green.
Eliminate ~100,000× disk write amplification caused by the old enforceMaxLogSize loop (read 10MB + truncate + write 10MB per 1024-byte PTY chunk). Under TUI redraw workloads, this could generate 280MB+ of accumulated disk writes in the data directory. Changes: - Add RingBuffer (internal/instance/logbuf.go): bounded, thread-safe, in-memory ring buffer. Backing slice pre-allocated eagerly. Hot-path optimized with WriteString (no []byte(chunk) copy) and atomic pointer access (no stateMu per chunk). - Add adaptive sizing (internal/instance/sizing.go): per-instance cap clamp(available/16, 16MB, 256MB), default 32MB. Global budget capped at 25% system RAM. User override via config.GlobalConfig.LogBufferBytes. - Refactor Manager (internal/instance/manager.go): pumpLogs writes to ring buffer instead of disk. Tail/ReadSince read from buffer. Removed enforceMaxLogSize, logPathByID, and all LogPath references. - Remove LogPath from ManagedInstance (internal/store/state.go). Old state.json files with log_path load cleanly (JSON silently ignores). - Add PurgeOrphanLogFiles() called at daemon startup to clean up dead .log artifacts left by pre-buffer code. - HTTP/MCP: budget-exceeded returns 503 with structured log_buffer_budget_exceeded body. UI shows dedicated modal. - Test coverage: RingBuffer (15 tests), sizing/budget (9 tests), manager budget integration, backward-compat state loading. - Docs: PRD, ARCHITECTURE §4.1, API (error shape + cursor semantics), CHANGELOG Unreleased section, and detailed implementation plan. BREAKING: state.json schema — log_path field removed. Old files continue to load; external tooling should drop log_path dependency.
- Stats API now includes daemon_cpu_percent/daemon_memory_bytes in global totals - Per-instance stats expose memory_buffer_bytes (used) and memory_buffer_cap_bytes (cap) - Collector refactored: system calls moved outside lock, 3-phase pipeline - UI adds mw daemon row and buffer used/cap annotation per instance - Add .omo/ to .gitignore for OpenCode internal session data
Halves the per-second process.NewProcess() syscall storm when the resource monitor modal is open, reducing daemon CPU overhead.
fix: replace per-instance disk log with in-memory ring buffer
Add Diff and Content APIs, preview.html, highlight.js/marked.js vendors, security helpers, and 16 test cases. Based on docs/plans/text-file-preview/.
…ked diff Build on the text file preview feature with three iteration-driven additions (FEASIBILITY/TASK updated to record design deviations): - preview.html: per-line flex containers with line-number gutter; hljs splitHighlightedLines tracks unclosed <span> across lines so multi-line tokens (block comments, raw strings) keep their colour. Diff keeps hljs token-level text + CSS row-level background. - app.go: pass -c core.quotePath=false to every git call and add unquoteGitPath as defense-in-depth, so non-ASCII paths survive core.quotePath quoting in numstat, status, and diff outputs. - app.go: handleWorktreeFileDiff synthesises a full unified diff when git returns empty (untracked file), with Stat pre-check + post-build re-check to honour the 500KB cap and \ No newline at end of file. - app_test.go: TestUnquoteGitPath (12 cases incl. UTF-8), plus Synthetic / SyntheticNoNewline / SyntheticTooLarge for the new path. - docs/API.md: document the two endpoints and the synthetic diff behaviour so external consumers know what to expect.
gofmt -l flagged both files for missing final newline. Pure whitespace fix; no behavioural change.
feat(preview): line numbers, quotePath handling, synthetic untracked diff
# Conflicts: # CHANGELOG.md # docs/PRD.md # internal/app/app_test.go # internal/ui/static/index.html
PR #40 (GitHub sidebar link) and PR #42 (branch divergence detection) both inlined a ~10-line `git remote` parser in their respective files. Git's content-level merge cannot detect duplicate top-level symbols across files, so the collision only surfaced as a Go compiler error after the merge. The fix was to drop the divergent.go copy in the merge commit, leaving a single implementation in remote.go. This commit goes one step further: move that single implementation to a new remotes.go file and export it as ListRemotes, so future features that need the same helper have a clearly signposted canonical entry point instead of being tempted to inline their own copy. No behavior change. The two existing callers (GitHubURL in remote.go and effectiveHead in diverged.go) now invoke ListRemotes directly.
`gofmt -l` on Linux/macOS CI rejects the file without a trailing newline. Run gofmt to comply.
feat: surface github.com link in sidebar main workspace
整合 reasonix web UI 嵌入调研(方案 A 反代+iframe 推荐)+ 三项实测结论: - session lease 按 session 文件路径互斥,多 worktree 各起一个 serve 可并行 - 端口以 --port-file 为权威;serve 无自动递增、web 自动 +1 - /events 每 15s 自带 : ping 保活;Cookie: reasonix_token 注入实测 200 无重定向 - 布局问题:侧栏 220px 固定,需反代 + 轻量注入解锁折叠(§9)
Add a second instance type: kind=reasonix runs a per-worktree 'reasonix serve' subprocess and renders its web chat UI in an iframe via a same-origin reverse proxy at /rx/<id>/. - store: ManagedInstance.Kind (tty|reasonix, omitempty, no migration) - internal/instance/reasonix: driver with isolated REASONIX_HOME, ~/.reasonix config/.env symlinked in, kernel-assigned port via --port-file, fixed session file, cross-platform liveness (kill(pid,0) + TCP probe, no /proc), SIGTERM->SIGKILL stop - manager: kind branch for Start/Stop/Restart/Delete; Reconcile re-attaches live serve processes on startup (R-02); Delete stops before wiping state dir (N-01) - internal/app/reasonix_proxy: /rx/<id>/ reverse proxy with reasonix_token cookie injection, RawQuery stripped, Accept-Encoding forced to identity, SSE streaming, HTML URL-prefix rewrite for fetch/EventSource/XHR - frontend: iframe rendering branch, create-dialog checkbox, rx badge - docs: API §5.10, ARCHITECTURE §3.2/4.2, PRD §7, plans index + FEASIBILITY §9.5 security tradeoff, DEFERRED notes Two rounds of code review applied (R-01..R-08, N-01..N-06, T-01..T-03); follow-ups tracked in issues #43..#49. Pre-existing app test failures (TestHandleInstanceUpdate/Reorder, real-user state dir) tracked in #43.
…r-side Runtime issues found when exercising the MVP: - iframe reloaded every 2s poll tick: renderWorkspace() -> ensureWebFrame() assigned frame.src unconditionally; guard it with a dataset.instance check so the embedded page only navigates when the instance changes (trailing slash added so in-page relative URLs resolve under /rx/<id>/). - <img src="/assets/logo-wordmark.svg"> 404'd: the URL-prefix shim only covered fetch/EventSource/XHR. Rewrite root-relative src/href/action/ poster attributes server-side in ModifyResponse (rewriteRootAttrs) so the browser never issues a wrong first request, and keep a MutationObserver for dynamically inserted nodes. rewriteRootAttrs (two-level tag->attr regex): - all attributes in one tag rewritten (multiple-attrs case) - case-insensitive (?i), unquoted values, idempotent (already-prefixed and protocol-relative //host URLs skipped), data-src/xlink:href and JS property assignments (el.src=) not matched - documented regex tradeoffs (value containing '>' truncates; no second pass for initial page; CSS url()/srcset/SVG <use> uncovered) Tests: TestRewriteRootAttrs grown to 13 cases incl. the previously uncovered risk path (two attrs in one tag), verified via control experiment; proxy HTML-injection test now asserts real rewritten output instead of grepping for script source.
GitHub macOS runners have broken/unresponsive DNS, and HTTPServer.server_bind() calls socket.getfqdn() for a reverse lookup that hangs indefinitely. The fake reasonix serve scripts (python3 http.server) never wrote --port-file, so every reasonix test timed out with 'serve did not become ready within 15s' on macOS only. Patch socket.getfqdn to skip the DNS lookup in the test scripts.
feat(reasonix): embed reasonix web chat UI as an instance kind (MVP)
Finish every open reasonix issue plus the deferred env/preStart injection: - #44 security: independent loopback listener serves only /rx/, iframe loads the cross-origin web_url from /api/instances so embedded chat content can no longer silently call the management API; TLS mode falls back to the same-origin /rx/ route (documented in DEFERRED.md). - #48 layout: single HTML injection point adds desktop sidebar collapse (collapsed by default, own toggle button, --mw-sidebar-w width), narrow screens keep the native mobile UI. - #46 perf: driver caches {port, token} for the instance lifetime (Start writes, Stop/Cleanup drop) so the proxy path reads no files. - #47 locking: Delete and startReasonix failure paths run Reasonix.Stop/ Cleanup outside stateMu (5s worst case no longer stalls the manager). - #45 robustness: pre-start 'reasonix --version' gate (>= 1.22.0), readiness errors carry serve.log tail, reasonix.CookieName single source; fixes a latent Start deadlock on early serve exit. - #43 tests: app handler tests use isolated temp data dirs + seeded state, reorder passes the optimistic-lock version (new 409 case). - deferred env/preStart: StartInput.Env + PreStart hook, tag env/ preStart flow into the serve process (command still ignored). - #49 closed as intended: Restart stays a brand-new conversation. Docs (API/ARCHITECTURE/PRD/DEFERRED/README) and full test suite updated; all tests pass, all 7 issues closed.
Upstream .app is a 2x2 grid whose .transcript/.footer rely on
auto-placement: .sidebar (grid-row:1/3) claims column 1, pushing them to
column 2. display:none on the collapsed sidebar reflows auto-placement —
.transcript lands in the 0px column (chat area vanishes) while .footer
stretches across row 1 (only the input bar stays visible). Pin
.mw-rx .transcript{grid-column:2;grid-row:1} and .mw-rx .footer{grid-column:2;grid-row:2}
so collapsed layout keeps upstream's column-2 row-1/row-2 slots.
…twork-open The independent #44 listener reports an absolute http://127.0.0.1:<port> web_url, which a remote browser resolves to ITSELF — with the default 0.0.0.0 listen (or an explicit LAN IP) the reasonix iframe was blank on LAN access. Enable the independent listener only when the main listener is loopback-only (and non-TLS); network-open or TLS modes fall back to the relative same-origin /rx/<id>/ path, which follows the browser's current origin, so LAN/remote access works and switching IPs after startup keeps working. Frontend needed no change (web_url empty → it already falls back). Docs (API/ARCHITECTURE/DEFERRED/README) updated; new TestReasonixNonLoopbackFallsBack covers the fallback.
Behavior parity with tty instances: a tty shell dies when its PTY hangs up on process exit, but a reasonix serve has no PTY and previously survived shutdown as an orphan (re-attached by Reconcile on next start). Server.Shutdown now calls Manager.StopAllReasonix before closing the HTTP servers: every running reasonix serve gets a graceful Stop (TERM→KILL), state dirs are kept so the next Start --resumes the same session.jsonl and the conversation survives. kill -9 still bypasses Shutdown — the serve then lingers and Reconcile re-attaches it (documented). Tests: TestStopAllReasonix, TestServerShutdownStopsReasonix.
…tart lock, encoding negotiation - proxy: strip only myworktree's ?token= and forward other query params (e.g. ?session=) intact to the reasonix backend - proxy: force Accept-Encoding identity only on HTML navigation requests; explicitly negotiate gzip for subresource/API so Go's Transport forwards compressed bodies verbatim; ModifyResponse refuses to splice into a still-compressed HTML response - driver: serialize Start per instance id (in-flight lock) so concurrent Starts cannot both spawn serve processes racing on the same port/pid files; second caller sees the healthy process and returns idempotently - driver: remove stale pid file alongside port file before spawning so waitReady cannot pair an old pid with the new port - tests: query pass-through, encoding negotiation, compressed-HTML guard, concurrent-Start lock (verified it fails without the lock); instanceView JSON round-trip intent documented
- issue #53: hide the web frame (display:none) instead of destroying it on tab switch, so chat drafts / scroll / sidebar session list survive; re-navigate only when the resolved src changes (new port after restart) - issue #54: hide leftover positioned xterm hosts when a reasonix tab is active so they cannot overlay/block the static iframe - issue #55: reset terminal-container opacity/filter on switching to a reasonix instance so a leftover grayscale never greys out the web UI - stop->start regression: invalidate the frame's src cache when an instance is stopped so the next start re-navigates instead of showing the stale pre-stop page - add TestIndexHTMLCoversReasonixWebFrame regression test
…on pool (issue #56) Each reasonix instance previously ran with an isolated REASONIX_HOME, so the embedded web UI never saw the project's existing history (sidebar empty, 'branches: none'). Requirement revision: myworktree should not intervene in the agent's product logic — the serve now uses the user's real ~/.reasonix, so sessions/history/config/credentials are shared per project exactly like a terminal-run reasonix, and the same project's history (including terminal CLI/TUI sessions) is visible/switchable in the sidebar. Cross-project isolation is reasonix's own (per-cwd). - Remove home dir, config/.env symlinks, session.jsonl, --resume and the REASONIX_HOME env injection; state dir now only holds token/port/pid/serve.log. - Strip inherited REASONIX_HOME/REASONIX_STATE_HOME from the serve env (log key names only) with tag-env opt-out; legacy home/ leftovers get a migration hint on Start. - Cleanup never touches the shared ~/.reasonix pool; issue #49 'Restart = fresh session' semantics stay. - Tests: cross-platform env-strip via fake-serve echo, same-cwd concurrent instances, removeEnv edge cases. - Docs: PRD/ARCHITECTURE/DEFERRED/FEASIBILITY/README/CHANGELOG synced; upstream contract + owner recorded.
Add scripts/verify-reasonix-contract.sh as the single source of truth for the upstream reasonix behavior contract the driver depends on (version gate, per-cwd session layout, fresh-session uniqueness, lease refusal), reachable via an opt-in go test (REASONIX_CONTRACT=1) and a new CI workflow. Both CI paths SKIP (green) on runners without a reasonix binary until an install step is added. Document the contract and the REASONIX_HOME shared-pool behavior in README/ARCHITECTURE.
fix(reasonix): keep embedded web UI alive across tab switches
The injected hide/expand sidebar button in the embedded Reasonix web chat was 34x34px at (8,8), so its right side overlapped the conversation. It is now a vertical 24x64px pill at (2,8) showing a CSS arrow glyph that switches with state (▶ collapsed / ◀ expanded, pointing at where the sidebar moves — no text, no i18n). Measured against the upstream .transcript padding (24px 28px on desktop), the button's right edge (26px) stays inside the chat's 28px left gutter, so it never covers message text in either state; the taller target is easier to see and click. Layout tests now use grep-style per-property regexp assertions instead of tight substring matches, so CSS property reordering no longer breaks them.
…y, lock cleanup) - PATCH /api/instances and POST /api/instances/restart responses now render through instanceView, so reasonix instances keep their web_url like GET/POST - instanceView builds the response map directly, dropping the per-call Marshal->Unmarshal round-trip - preStart env now strips inherited REASONIX_HOME/REASONIX_STATE_HOME exactly like serve (shared reasonix.RemoveEnv), keeping both phases on the same ~/.reasonix home; host-exported overrides still apply via tag env - Manager.Stop tears down the per-instance serve-management dir and Start lock (Cleanup) after stopping, since the id is never reused; the driver's starts map no longer grows across start/stop cycles - delete confirmation copy corrected to "Delete instance?" - tests: PATCH web_url, Cleanup drops Start lock, preStart env strip, Stop cleans management dir; Restart cleanup test adapted (Stop now pre-cleans, seeds a stale dir instead)
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.
Release v0.4.0 — Reasonix native UI integration, PTY in-memory ring buffer logs, and workspace visibility improvements.
CHANGELOG updated (
## Unreleased→## v0.4.0 (2026-08-12)), plus 4 backfilled Highlights entries (text file preview, branch divergence badge,mw config regen, tags config directory).