Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ jobs:
- name: Verify release tag matches Strand version
run: node scripts/check-release-version.mjs

- name: Verify compatible helper downloads are published
run: pnpm release:check-helper

- name: Install Cosign
if: matrix.platform == 'ubuntu-22.04'
uses: sigstore/cosign-installer@v4.1.2
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 23 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,21 @@ the resolved app appearance automatically.

## Features

- **Repository settings** — identity and signing defaults live in each repository’s
context menu. Commit options holds per-commit signing; successful hook output
goes to Activity history. Tag signing stays under Advanced options.

- **Read-only command line companion** — `strand status/log/diff/review`
works without the desktop; `--json` emits a versioned typed payload and
`strand schema` describes it. Full-file review context uses the same engine
as the app. Settings → Integrations installs `strand` in
as the app. Settings → Integrations → Command line installs `strand` in
your user command directory. `strand PATH` opens and focuses a repository
in the existing desktop instance, including paths with spaces.

- **More hosting providers** — GitLab merge requests and Bitbucket Cloud pull
requests use the review workspace, with paged lists, comments, inline threads
and supported review decisions. GitHub Enterprise/custom hosts use their own
CLI authentication scope; select custom adapters in Settings → Hosting.
CLI authentication scope; select a custom provider in Edit remote → Advanced.
GitLab merges guard the reviewed head and follow project settings. Bitbucket
merge, GitLab request-changes and Bitbucket draft transitions remain
provider-site actions. Bitbucket Server is not supported.
Expand All @@ -91,18 +95,21 @@ the resolved app appearance automatically.
Interrupted creation and remote setup can be resumed from the same dialog.

- **Personal user actions** — define repository, ref, and working-tree file
commands in Settings → Integrations. Menu and palette entries open an exact
commands in Settings → User actions, with one argument per row. Contextual
Actions menus and palette entries open an exact
executable/argument/working-directory preview, with bounded output and cancellation.

- **Repository size controls** — clone a chosen branch with optional depth,
single-branch fetching, on-demand file contents (`blob:none`), and recursive
submodules. Inspect clone scope and download more or full history from the
network menu or palette. Sparse checkout selects cone directories, distinguishes
excluded files from deletions, and preserves external sparse indexes on reads.
Fetch submenu or palette. Files → File actions opens folder selection for
sparse checkout. Excluded files remain distinct from deletions, and reads
preserve external sparse indexes.
Selection changes refuse dirty trees and ignored-file removal.

- **Git LFS** — repository setup, tracking patterns, object/transfer status,
downloads/uploads and server locks from the sidebar and command palette.
downloads/uploads and server locks from repository menus and the command palette.
File menus prefill a tracking pattern or lock target.
Whole-file staging, checkout, discard and hard reset honor LFS filters;
history is never migrated.
- **Submodule lifecycle** — add, remove, deinitialize, synchronize URLs, and
Expand Down Expand Up @@ -209,9 +216,10 @@ the resolved app appearance automatically.
Explicit GitHub merge queue/auto-merge and Azure auto-complete controls show
provider capability, enable/cancel, policy state and queue position separately
from a completed merge.
**Review tools** saves a reviewed head, compares exact trees across pushes or
rebases, exports all unresolved feedback with replies, and previews validated
standard suggestions before applying them to a clean local file.
Code’s toolbar saves a reviewed revision and compares changes since the last
review. The PR menu exports unresolved feedback; each suggestion offers a
before/after preview. Section-level controls load more discussion and checks.
Automatic merging and queue controls live in Merge options.
Every GitHub or Azure PR can open its exact provider head in a new worktree
without changing local refs or `FETCH_HEAD`. Active PRs can be closed from a
confirmed overflow action. Closed PRs can be reopened;
Expand All @@ -223,7 +231,8 @@ the resolved app appearance automatically.
independently versioned and updated, signed `strand-azdo` REST helper from
a protocol-specific release channel, configured in Settings → Hosting;
installation shows an explicit download and verification indicator, and
Retry force-replaces a broken or protocol-incompatible installed helper;
Retry force-replaces a broken or protocol-incompatible installed helper.
Unavailable helper releases are identified in the installation error.
PATs live only in the native credential vault, and Windows can use integrated
Negotiate/NTLM authentication. Its collection URL automatically matches HTTPS
repository remotes and supplies the project/repository coordinates used by the
Expand Down Expand Up @@ -288,8 +297,8 @@ the resolved app appearance automatically.
- **Advanced Git refs** — inspect and edit Git notes and replacement refs;
retarget or re-annotate existing unsigned tags with old/new targets and
optional remote publication checks. External edits require a fresh review.
- **Guided bisect** — start from known good/bad revisions, test and mark
good/bad/skip, inspect remaining candidates and the culprit, resume external
- **Guided bisect** — use a commit’s Find regression menu to choose working
and broken revisions; test and mark working/broken/skip, inspect remaining candidates and the culprit, resume external
sessions, and reset to the original checkout while protecting test edits.
- **Commit graph** — SVG lanes with branch/tag chips, revealable inline stash
nodes with non-mutating diff inspection, a
Expand Down Expand Up @@ -348,7 +357,8 @@ the resolved app appearance automatically.
Review findings are structured, path/line-validated, stale-diff guarded, and
require explicit acceptance before they become notes; repository files are
never changed by an AI review.
- **Read-only SSH repositories** — inspect remote status, history, full-context
- **Read-only SSH repositories** — open from the repository `+` menu or palette
to inspect remote status, history, full-context
reviews and bounded file snapshots through system OpenSSH, with watching,
connection health, cancellation and reconnect. Requires a manually installed
compatible companion on the POSIX host; see the
Expand Down
19 changes: 19 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2979,6 +2979,25 @@ unsigned overrides, tampering and failed signers. Native Windows settings,
commit/amend and palette tag flows passed; validation and platform limits are
recorded in `docs/hooks-identity-signing-validation-2026-09-06.md`.

**UI placement and readability revision (2026-09-06):** Moved the September 6
feature controls into their owning repository, file, branch, tag and PR menus.
Repository settings separates identity and signing from global preferences;
User actions has its own settings page and individual argument fields. Code owns
review actions and inline suggestion previews, section footers own paging, and
Merge options owns automatic completion. Advanced clone, signing and setup
settings collapse; long dialogs scroll within the window with visible controls.
This placement supersedes the earlier September 6 surface descriptions above.
Browser interaction checks and validation limits are recorded in
`docs/ui-placement-validation-2026-09-06.md`.

**Azure helper download repair kick (2026-09-06):** The app advanced to helper
protocol 7 while only protocol 5 was published; Strand 1.5.1's protocol-6
channel is also missing. Helper 1.3.0 is prepared for protocol 7, installer
404s now identify unavailable or incomplete releases, and desktop release CI
checks the public manifest, signature, and every platform archive before
building. Signed helper publication and a separate protocol-6 backfill remain
pending; protocol compatibility and signature enforcement stay in place.

## Cross-cutting tracks (run in parallel with all milestones)

**Performance audit kick (2026-09-06):** Rechecked `main` at `8e83c8c` on
Expand Down
23 changes: 23 additions & 0 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ Detailed comparison and sequencing: [`docs/git-client-1.0-audit.md`](./docs/git-

---

## UI placement and readability (2026-09-06)

- ☑ Move the September 6 feature controls into repository settings, contextual
menus and PR content; simplify labels and help text; verify keyboard access
and browser layouts (`repositoryTools`, `RepositorySettingsDialog`, per-section
PR paging, inline suggestion entry points, Merge options, and
`docs/ui-placement-validation-2026-09-06.md`).
- ☐ Run the revised placements in native Windows WebView2; the isolated launch
was blocked by automatic approval review. Browser checks cover the UI, while
native IPC/signing/SSH/provider behavior needs that final integration pass.

## strand-core (Rust git engine)

### Git-client feature audit follow-ups (2026-09-06)
Expand Down Expand Up @@ -2058,6 +2069,18 @@ tree: watch the agent work, review fast, accept or reject safely.
promoted to `strand-azdo-protocol-5` plus the legacy `strand-azdo-latest`
channel; hosted post-promotion Linux smoke passed and all three manifests
matched byte-for-byte (`Release` run `30427932365`; 2026-07-29).
- ◐ Repair missing Azure helper downloads (2026-09-06): protocol 7 has no
published channel; helper 1.3.0 is prepared locally. Desktop release builds
now require the matching public manifest, signature, and all three archives
(`check-azdo-channel.mjs`); installer 404s explain a missing or incomplete
release (`azdo_helper::download`). Signed publication of
`strand-azdo-v1.3.0` / `strand-azdo-protocol-7` and installation verification
remain pending. Local validation: 19 Rust helper/lifecycle/protocol tests,
9 release-script tests, Rust check, TypeScript check, and release security
policy passed; the built Windows helper reports version 1.3.0 / protocol 7.
- ☐ Backfill the signed protocol-6 helper channel for already-published Strand
1.5.1 from its compatible source, with a new immutable helper version/tag;
publishing protocol 7 cannot repair protocol-6 clients (2026-09-06).
- ☑ DAN-64 helper lifecycle recovery (`azdo_helper::download_and_install` force
mode + protocol-only manifest gating, direct vault cleanup, and removable
incompatible-binary status; 2026-09-02): Hosting Retry always replaces the
Expand Down
2 changes: 1 addition & 1 deletion crates/strand-azdo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "strand-azdo"
version = "1.2.1"
version = "1.3.0"
edition.workspace = true
license.workspace = true
description = "Optional Azure DevOps Server REST helper for Strand."
Expand Down
71 changes: 71 additions & 0 deletions crates/strand-tauri/src/azdo_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,19 @@ fn download(client: &reqwest::blocking::Client, url: &str, limit: usize) -> Resu
.send()
.map_err(|error| format!("Could not download strand-azdo: {error}"))?;
if !response.status().is_success() {
if response.status() == reqwest::StatusCode::NOT_FOUND {
return Err(if url.ends_with("/strand-azdo-manifest.json") {
format!(
"No compatible strand-azdo helper release was found for this Strand build \
(protocol {PROTOCOL_VERSION}, 404 Not Found). Try again after the helper release is published."
)
} else {
format!(
"The strand-azdo helper release for protocol {PROTOCOL_VERSION} is incomplete \
(404 Not Found). Try again after the helper release is repaired."
)
});
}
return Err(format!(
"Could not download strand-azdo ({})",
response.status()
Expand Down Expand Up @@ -700,6 +713,64 @@ mod tests {
);
}

#[test]
fn missing_helper_downloads_explain_the_release_failure() {
use std::net::TcpListener;

for (file, status, expected) in [
(
"strand-azdo-manifest.json",
"404 Not Found",
"Try again after the helper release is published",
),
(
"strand-azdo-manifest.json.minisig",
"404 Not Found",
"Try again after the helper release is repaired",
),
(
"helper.zip",
"404 Not Found",
"Try again after the helper release is repaired",
),
(
"strand-azdo-manifest.json",
"503 Service Unavailable",
"503 Service Unavailable",
),
] {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!("http://{}/{file}", listener.local_addr().unwrap());
let server = thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
stream
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let mut request = [0; 4096];
stream.read(&mut request).unwrap();
write!(
stream,
"HTTP/1.1 {status}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
)
.unwrap();
});
let client = reqwest::blocking::Client::builder()
.no_proxy()
.timeout(Duration::from_secs(5))
.build()
.unwrap();
let error = download(&client, &url, 1024).unwrap_err();
server.join().unwrap();
assert!(error.contains(expected), "{error}");
if status.starts_with("404") {
assert!(
error.contains(&format!("protocol {PROTOCOL_VERSION}")),
"{error}"
);
}
}
}

#[test]
fn protocol_mismatch_downloads_the_matching_channel_helper() {
let manifest = manifest("1.2.2", PROTOCOL_VERSION);
Expand Down
24 changes: 24 additions & 0 deletions docs/learnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -2589,6 +2589,13 @@ metadata. Sharing an updater fast path with explicit Retry made recovery a
no-op; requiring a protocol-compatible helper to remove its own state trapped
users after a breaking IPC change.

**Protocol changes require publication before desktop release (2026-09-06).**
Changing the shared protocol makes Install helper request a new release URL.
Desktop tags do not build helpers. Run `release:check-helper` before desktop
packaging and publish a newly versioned helper first if that channel is
missing; keep prior channels available for shipped clients. Never redirect an
unpublished protocol to global latest or weaken verification to hide a 404.

## Performance evidence must separate status, patches, and paint (2026-09-06)

A fast `repo_snapshot` does not establish a fast refresh: the frontend can
Expand Down Expand Up @@ -2764,3 +2771,20 @@ Preserve that distinction in settings displays and scoped editing.
LFS guards must run before sparse-index mutation dispatch. Refresh an attached
memory-only sparse index through `sparse_read_index`, never `Index::read` from
disk; keep one process-tree cancellation helper when composing Git workflows.

### 2026-09-06 — Place occasional Git tools with their context

The user rejected the accumulated UI controls in PRs #115–122. Keep the main
work surfaces quiet: put repository identity/signing in Repository settings,
per-commit signing beside Commit, file scope under Files, Git-flow under Branches,
imports/exports under repository menus, and PR actions beside the content they
affect. User actions has a separate settings page with individual argument rows;
do not expose JSON editing for ordinary configuration. Keep advanced setup and
source diagnostics collapsed. Menus must capture their exact repository/ref/file,
including inactive repository tabs, and remain reachable by keyboard and palette.

Check shared dialog placement against the actual backdrop offset: the current
backdrop starts 96px below the window top. A max-height of 88vh or 100vh minus
64px can clip the footer. Reserve that offset plus bottom space, let the body
scroll, and prevent header/footer and form controls from shrinking. Verify both
1280×800 and 880×600. Readable text alone does not establish a readable layout.
12 changes: 10 additions & 2 deletions docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ versions are independent from Strand versions. Run `pnpm version:azdo patch`
matching `strand-azdo-vX.Y.Z` tag. Normal `vX.Y.Z` Strand releases do not rebuild
or renumber the helper.

Before a desktop release, `pnpm release:check-helper` reads its compiled
protocol from the shared contract and checks the public channel manifest,
signature, and all three platform archives. The desktop release jobs require
this check before building installers. Publish a compatible helper first when
the protocol changes; bumping only Strand otherwise leaves Install helper
pointing at a 404. The check covers availability; the desktop installer still
enforces the signature and archive/binary hashes.

The helper tag builds universal macOS, Windows x86_64, and Linux x86_64
`.zip`/`.tar.gz` archives. Each runner executes its built binary's
`version --json`; the metadata and manifest jobs fail unless all three binaries
Expand All @@ -202,8 +210,8 @@ manifest to a draft versioned helper release, publishes it as a prerelease, and
then promotes the identical
artifacts to `strand-azdo-protocol-N`. Strand constructs that channel from its
compiled protocol version, so publishing protocol N+1 cannot break reinstall
for an older Strand release. The current thread-lifecycle contract is protocol
6. Protocol 5 is additionally promoted to the legacy
for an older Strand release. The current deferred-completion contract is protocol
7. Protocol 5 is additionally promoted to the legacy
`strand-azdo-latest` channel used by already-published Strand 1.2 clients. A
post-promotion Linux smoke job downloads through the protocol channel, executes
the published helper, and rechecks its version, protocol, archive/binary hashes,
Expand Down
4 changes: 4 additions & 0 deletions docs/release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Run from the repository root on the exact candidate commit:
pnpm install --frozen-lockfile
pnpm release:check-security
pnpm release:test-helper
pnpm release:check-helper
pnpm store:check
pnpm --filter ./ui exec tsc --noEmit
pnpm --filter ./ui exec vitest run
Expand All @@ -32,6 +33,9 @@ to the matching
`strand-azdo-protocol-N` channel. The post-promotion smoke job must download
through that channel and match the running Linux binary and archive to the
manifest. A normal Strand tag must not renumber or publish the helper.
Before building a desktop release, its exact protocol channel must already
serve the manifest, signature, and all supported platform archives. Publish
the matching helper first; retain older channels for already-shipped apps.

## Publisher and update trust

Expand Down
Loading
Loading