Skip to content

feat: add v0.2.0 bootstrap foundation - #3

Open
kefeiqian wants to merge 52 commits into
mainfrom
feat/v0.2.0-bootstrap-foundation
Open

feat: add v0.2.0 bootstrap foundation#3
kefeiqian wants to merge 52 commits into
mainfrom
feat/v0.2.0-bootstrap-foundation

Conversation

@kefeiqian

Copy link
Copy Markdown
Owner

Boundary

Bootstrap foundation for the v0.2.0 queue:

  • streaming-ready Rust/TypeScript backend protocol (turnId, submit status, token/turn notifications)
  • backend-owned kqode.git.status and TUI git-status state
  • prompt queue state for active/queued/settled turns and coalesced streaming deltas
  • composer soft-wrap scrolling, wheel routing, caret suppression while scrolling, and mouse click placement

Provider 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.0 at ab197508f8b811414bc25f771cc5754e982d96e4.

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

045069a54b8e0c05f2ea40f83fbeccab6b53585b

Folded fixes

  • Kept bootstrap tests with their owning behavior: Rust JSON-RPC submit/error/git-status tests, TUI backend client/protocol/runtime tests, prompt queue tests, composer scroll/windowing tests, terminal mouse tests, and git-status atom tests.
  • Folded the source-branch mixed WIP behavior into a final-state bootstrap branch instead of preserving WIP commit boundaries.
  • Folded review fixes before opening: bounded git-status execution, streaming idle timeout, non-fatal best-effort git refresh, stale active-turn settlement guard, caret suppression cleanup, hard-exit mouse tracking restore, and source-file splits under the TUI size guideline.

Extraction notes

  • Included: backend protocol foundation, git-status backend/TUI seam, streaming-ready prompt queue, composer scroll and wheel/click handling.
  • Excluded: .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.submit accepts the later streaming-ready wire shape but returns needsConfiguration in this bootstrap branch. It does not read workspace .env files or plaintext credentials; provider setup lands in the dedicated provider PR.
  • The old source branch name is only used as release-snapshot context. The review branch is function-oriented: feat/v0.2.0-bootstrap-foundation.

Validation

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo build --workspace --all-targets
  • cargo test --workspace
  • cargo xtask tui-typecheck
  • cargo xtask tui-test
  • gitleaks git . --log-opts='origin/main..HEAD' --redact --no-banner

Queue approval

Initial approved queue item, with branch naming corrected per user direction to use v0.2.0-{function name} instead of the old tui-composer-scroll source-branch label.

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
kefeiqian force-pushed the feat/v0.2.0-bootstrap-foundation branch from 42f01bd to a9a5243 Compare July 14, 2026 09:32
kefeiqian-microsoft and others added 22 commits July 14, 2026 18:06
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
kefeiqian force-pushed the feat/v0.2.0-bootstrap-foundation branch from fac2cb0 to 309ed42 Compare July 18, 2026 02:12
kefeiqian-microsoft and others added 4 commits July 22, 2026 15:39
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
kefeiqian and others added 24 commits July 23, 2026 14:34
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
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>
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