feat: add v0.2.0 bootstrap foundation#3
Open
kefeiqian wants to merge 52 commits into
Open
Conversation
Extract the first review branch from the frozen release candidate with the streaming-ready backend protocol, git-status backend/TUI seam, prompt queue streaming state, and composer scroll support. Keep provider configuration out of this bootstrap branch: submit returns needsConfiguration without reading plaintext credentials, leaving provider setup for its dedicated queue item. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4c127c9c-ac00-4a90-be0f-4357720a6a8d
Bound git-status and streaming failure paths, keep best-effort git refreshes from killing the session, reset caret and mouse terminal modes on teardown, and add focused regression tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4c127c9c-ac00-4a90-be0f-4357720a6a8d
kefeiqian
force-pushed
the
feat/v0.2.0-bootstrap-foundation
branch
from
July 14, 2026 09:32
42f01bd to
a9a5243
Compare
Expose an optional GitHub pull request label from the Rust git status request and render it as a separate cwd segment in the TUI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c551aab9-42c3-43ae-b8ff-3a9cd25d6f23
Extend the git status backend so the TUI can later render the PR label as a hyperlink. 'gh pr view' now requests 'number,url' (parsed as JSON), and WorkspaceGitStatus/GitStatusResult carry a new pullRequestUrl beside the existing pullRequestLabel. Kept the Rust<->TS protocol in lockstep (protocol.rs, backend.rs, messages.ts, client.ts, messageConnectionClient.ts) and updated the unit, integration, and protocol tests. Also removes the unused status_label() helper, which was dead code superseded by status(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d79c1bb5-e2b0-46da-be51-55e483f04bf5
Decorate the cwd row's pull-request segment (e.g. #3) so supporting terminals show it as a clickable OSC 8 hyperlink with a dotted-underline affordance. New pure builders in libs/terminal/hyperlink.ts wrap the label; cwdLine.ts gains renderCwdLine (display) beside formatCwdLine (plain), and CwdLine.tsx renders the decorated string. The label is decorated only when the status carries a pull_request_url, so a bare label never implies a dead link. formatCwdLine stays plain and remains the source of truth for countCwdRows' width math, so the zero-width escapes cannot shift the cursor-sensitive bottom rows. Note: Ink's slice-ansi drops a lone 4:3 (dotted) underline reset, so dottedUnderline leads with a standard 4 underline to keep the 24m reset paired; 4:3 then upgrades to dotted where supported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d79c1bb5-e2b0-46da-be51-55e483f04bf5
The TUI owns the mouse (SGR tracking), so a plain left click on the cwd line's PR label now opens the pull request in the browser. resolvePullRequestClickTarget hit-tests the label span using the same hard-wrap model as countCwdRows (so wrapped rows map correctly), and openExternalUrl launches the OS opener (cmd start / open / xdg-open) as a detached argv process, validating the URL is http(s) so a hostile URL cannot inject a command. Wired via a focused usePullRequestClick hook to keep HomeScreenView under the ~200-line guideline. pullRequestLabelOffset (in cwdLine.ts) locates the label; the label is only actionable when a pull_request_url is present. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d79c1bb5-e2b0-46da-be51-55e483f04bf5
…spawn crashes Address two issues found reviewing the PR-click opener. The current caller passes a trusted gh URL, but the libs/os util's contract must hold for any caller. - Windows command injection: routing through `cmd /c start` let cmd re-parse metacharacters in a space-free URL, and it also broke legitimate query URLs that contain an ampersand. Every platform now uses a non-shell opener (explorer.exe / open / xdg-open) invoked directly, so URL characters are passed literally and cannot be reinterpreted as commands. - Async spawn crash: spawn reports a missing opener via an async `error` event, not a throw, so the try/catch could not keep it best-effort. An `error` listener is now attached so a missing binary (e.g. Linux without xdg-open) is swallowed instead of crashing the input loop. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d79c1bb5-e2b0-46da-be51-55e483f04bf5
…nt directory Add a `tui-dev-here` command that launches the TypeScript TUI with the caller's current working directory as the workspace, mirroring how the packaged `kqode` executable runs from a project directory. This makes it possible to dogfood the source-mode TUI against any project on disk without touching the fixture machinery. Extract TUI argument forwarding into a shared `tui::args` module so `tui-dev`, `tui-dev-here`, and `tui-prod` all forward extra CLI args (e.g. `--resume`, `--debug`) to the TUI, and refactor `dev::ensure_workspace` to return the resolved workspace path so both the fixture and current-directory launch paths share `run_with_workspace`. `ensure_tui_dependencies` now always runs `bun install` so new manifest entries are picked up before launch. Document `tui-dev-here` in AGENTS.md and the README/CONTRIBUTING guides (English and zh-CN). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f44517c4-cfef-4459-b3ca-fc5b1d50c5a3
…isolated build dir Make `cargo xtask` parallel-safe on Windows by building the xtask binary into a private `target/xtask` directory instead of the workspace `target/`, so a fast command never relinks a binary another `cargo xtask` or `cargo build --workspace` is holding: - `.cargo/config.toml`: the `xtask` alias now passes `--target-dir target/xtask`. - `scripts/xtask.ps1` / `scripts/xtask.sh`: build into `target/xtask` via `--target-dir` (never exported as `CARGO_TARGET_DIR`, so it cannot leak into child builds such as tui-prod's backend), and run per-invocation copies under `target/xtask/debug/xtask-run/`. - `.run/*`: convert the IDE run profiles to launcher-backed `ShConfigurationType` entries (or the `xtask <command>` alias form) so IDE runs inherit the private-dir isolation, and add a profile for the new `tui-dev-here` command. Update the AGENTS.md and CONTRIBUTING (English and zh-CN) launcher notes to describe the parallel-safe behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f44517c4-cfef-4459-b3ca-fc5b1d50c5a3
…nsistency Match the sibling read_status and the module's verb-prefixed helpers read_, parse_, format_. The function does I/O by spawning gh, so a verb signals that better than a bare noun. Rust convention avoids get_ prefixes per API Guidelines C-GETTER, so read_ fits rather than get_. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d79c1bb5-e2b0-46da-be51-55e483f04bf5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2b0df915-e74d-4daa-88ac-85f27c3c7954
The workspace status ran gh pr view under the same 2s timeout as the local git status call, but the PR lookup makes a network round-trip to the GitHub API whose latency regularly exceeds 2s (measured up to 3.4s). When it did, run_stdout killed gh and dropped the PR label and hyperlink from the cwd line, so the #N segment appeared only intermittently. Split the shared timeout into GIT_STATUS_TIMEOUT (2s, local) and PULL_REQUEST_TIMEOUT (8s, network), applied at each call site, and guard the relationship with a test so the networked lookup can not be starved below the local git budget. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d6d5666d-e29f-4367-8d45-f5e8848bd8f9
The submit path shipped a full streaming stack with no producer: the Rust backend always acks `needsConfiguration` and never emits tokenDelta/turnEnd/ turnError, so the TS client, delta coalescer, and live transcript rendering could only run against fake in-memory connections -- the e2e round trip was untestable in this bootstrap slice. Collapse it to the honest bootstrap contract (submit -> needsConfiguration ack, plus git status) and move the whole streaming stack forward to land with its real provider producer, so it arrives testable and final-state. Drops the kqode/tokenDelta|turnEnd|turnError methods and SUBMIT_STATUS_STREAMING on both sides, the delta coalescer, the streaming-text atom, and the stream idle/flush constants. `turnId` stays in the wire contract (the turn identity the ack echoes; tests/message_submit.rs asserts it), so the provider PR re-adds only the notification channel. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e1e09ee5-b826-490a-9c6e-32ede83ac053
The workspace status ran gh pr view on every turn as part of kqode.git.status, paying a 1-3s network round-trip each time even though a branch pull request is static for a session. Split the PR into its own kqode.git.pullRequest RPC: kqode.git.status now returns only the fast, local working-tree label (refreshed per turn), while the network-bound PR label and URL are fetched once at bootstrap. The per-turn label refresh preserves the bootstrap-fetched PR segment, so the cwd line keeps the #N link without re-querying gh. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d6d5666d-e29f-4367-8d45-f5e8848bd8f9
The submit contract no longer carries turnId (removed in d7d3252 alongside the pull-request work): MessageSubmitParams is `{ text }` with deny_unknown_fields and MessageSubmitResult is `{ status }`. These three submit tests still sent a `turnId` and asserted the echo, which no longer typechecks (destructuring a dropped field) and made the Rust integration submit reject on the unknown field. Update them to the `{ text } -> { status }` shape so the round-trip tests match the contract. turnId was inert client plumbing for future streaming correlation with no TUI-observable behavior, so it does not belong in this pre-provider PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e1e09ee5-b826-490a-9c6e-32ede83ac053
The source-mode backend is launched with a strict env allowlist that dropped APPDATA (Windows) and the XDG roots (Unix). gh loads its host auth config from %APPDATA%\GitHub CLI (or $XDG_CONFIG_HOME/gh), so gh pr view failed with an auth error inside the backend and the PR segment never rendered - while local git status (needing none of these) still produced the branch label. This was the primary reason the #N link never appeared live, independent of the request timeout. Admit APPDATA/LOCALAPPDATA and XDG_CONFIG_HOME/STATE_HOME/DATA_HOME to the allowlist. These are directory paths, not secrets, so provider keys and tokens stay excluded while gh can authenticate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d6d5666d-e29f-4367-8d45-f5e8848bd8f9
Removing the turnId field left the second request_frame(2, ...) short enough to fit on one line, so `cargo fmt --all --check` (CI) flagged the hand-written multi-line form. Collapse it to satisfy rustfmt. Formatting-only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e1e09ee5-b826-490a-9c6e-32ede83ac053
Replace the intermediate scroll-suppression behavior with the final v0.2 caret refresh mechanism so scroll repaints reassert the caret without hiding it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b9ba2c7-26f3-4177-8314-874d32ca1334
Drive PageUp through HomeScreen so the regression test verifies the scroll handler bumps the refresh tick and reasserts the same caret position. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b9ba2c7-26f3-4177-8314-874d32ca1334
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e25ad076-4980-42c3-8f70-892ad72a895d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e25ad076-4980-42c3-8f70-892ad72a895d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e25ad076-4980-42c3-8f70-892ad72a895d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e25ad076-4980-42c3-8f70-892ad72a895d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e25ad076-4980-42c3-8f70-892ad72a895d
kefeiqian
force-pushed
the
feat/v0.2.0-bootstrap-foundation
branch
from
July 18, 2026 02:12
fac2cb0 to
309ed42
Compare
Keep the scroll repaint subscription in a leaf component so body scrolling can re-assert the terminal caret without recomputing the full prompt composer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Consolidate mouse parsing tests on the production parser and delete test-only wrappers, formatting helpers, stale exports, and unused test utilities. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Codify the dependency layers, component export policy, constants purity, and cycle checks with explicit baselines for existing exceptions. Move modified-Enter sequences into the immutable constants layer as the first baseline reduction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Use TypeScript module resolution, reject unknown layers and mutable constant shapes, inspect component return paths, and cover bypass cases with fixture tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Move pure cursor and view logic into libs and relocate the stateful input controller into hooks so component modules export React components only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Move HomeScreen hooks and input control into hooks and relocate wheel routing and help-content formatting into libs, leaving component modules focused on React rendering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Place pure exit-summary formatting in libs and keep state access and terminal output in the CLI composition layer, eliminating the final non-component exports under components. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Move backend startup orchestration into the CLI composition layer and place the global key hook under hooks, leaving all architecture baselines empty. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Document domain-and-responsibility state organization and add a guard that limits state exports to atoms, selectors, and state-specific types with an explicit cleanup baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Recognize atom factories through their Jotai import bindings and prevent state barrels from re-exporting values outside the state layer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Separate composer base state, editing actions, cursor actions, and scroll state while moving cursor normalization and static initialization into lower layers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Separate transcript derivation, layout selectors, body scrolling, and composer scrolling into focused state modules with one-way domain dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Move exported static values into constants and split command, help, git-status, and prompt-queue state into focused state, selector, and action modules. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Treat shared wrap boundaries as the following visual row so vertical movement stays symmetric and the terminal caret remains inside the composer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Use one guarded chrome width for composer wrapping, caret movement, cwd layout and clicks, and the status label. BodyPane remains the only surface that renders scrollbar chrome in the final column. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Prevent Yoga stretch from painting the input row into the reserved final-column gutter before the first incremental repaint. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Document the screenshot-level right-edge observation and the lack of approved source evidence for the Copilot CLI renderer implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Patch Ink 7.1.0 so no-trailing-newline frames use the final visible row for normal, cursor-only, sync, and clear-terminal cursor positioning. Remove the application-level y offset and guard the patch with direct tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Represent the insertion point after an exactly full final line with a conditional empty visual row, while keeping vertical Up/Down navigation aware of that row for symmetric movement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Reserve one background-only column inside the composer and centralize its authored-text width across rendering, wrapping, caret movement, clicks, scrolling, and validation feedback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Invert half-block foreground and background colors so Terminal.app fills the input-colored half continuously, with inward-facing glyph fallbacks when color is unavailable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
This reverts commit 404f85d.
Use full-width horizontal composer borders with body-blended text rows in Terminal.app, and render user messages without decorative borders there. Other terminals keep the half-block treatment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Enable Kitty keyboard auto-detection and extend the Ink patch to parse xterm modifyOtherKeys Enter sequences, preventing Shift+Enter escape codes from being inserted as prompt text. Terminal.app keeps the backslash-Enter fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a618597-4cf6-4e8c-91a0-c9425055feb9
Reserve one viewport row only when TERM_PROGRAM identifies WezTerm so Ink stays on incremental rendering without changing edge-to-edge layouts in other terminals. Keep the minimum-height notice aligned with the terminal-specific row budget. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add transcript drag highlighting, right-click copy-or-paste behavior, and race-safe system clipboard integration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure the visible caret remains anchored to the composer text row while startup status temporarily locks input. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Exercise a blocked keypress before asserting that the caret remains on the composer text row. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
Boundary
Bootstrap foundation for the v0.2.0 queue:
turnId, submit status, token/turn notifications)kqode.git.statusand TUI git-status stateProvider configuration, persistence, session logging, clipboard primitives, and later transcript/queue lifecycle work are intentionally out of scope.
Representative commits
Source release snapshot:
release/v0.2.0atab197508f8b811414bc25f771cc5754e982d96e4.Representative source commits:
3280b57,43938c6,9249f87,115df2d,25b0561, reconstructed by hunk rather than cherry-picked wholesale.Dependencies
None. This is PR 01 and targets
main.Base SHA
045069a54b8e0c05f2ea40f83fbeccab6b53585bFolded fixes
Extraction notes
.env.example, README/provider setup docs,src/config*,src/provider*,src/chat*,src/debug_log*, docs/brainstorms, docs/plans, and all provider/login/session-logging/clipboard documentation.kqode.message.submitaccepts the later streaming-ready wire shape but returnsneedsConfigurationin this bootstrap branch. It does not read workspace.envfiles or plaintext credentials; provider setup lands in the dedicated provider PR.feat/v0.2.0-bootstrap-foundation.Validation
cargo fmt --all --checkcargo clippy --workspace --all-targets --all-features -- -D warningscargo build --workspace --all-targetscargo test --workspacecargo xtask tui-typecheckcargo xtask tui-testgitleaks git . --log-opts='origin/main..HEAD' --redact --no-bannerQueue approval
Initial approved queue item, with branch naming corrected per user direction to use
v0.2.0-{function name}instead of the oldtui-composer-scrollsource-branch label.