A MinVer dev build on main computes a prerelease of its own guessed next
version (e.g. 2.9.1-alpha.0.14 after tag v2.9.0), which already sorts
above the last tag by numeric core, so the update banner correctly
stays quiet there. But the reduction to a bare numeric core meant a
genuine new release landing at exactly that guessed version (v2.9.1
real) compared equal to the still-running prerelease and the banner
never fired. A prerelease now only loses to a real release of the same
core, not to another prerelease of the same core.
Verified against the live repo: latest published tag is v2.9.0; a
Release build of this worktree (14 commits ahead) reports
AssemblyInformationalVersion 2.9.1-alpha.0.14+<sha>.
MCP_TOOL_TIMEOUT (raised by ClaudeDo's launchers) is a wall-clock cap unrelated
to Claude Code's idle-silence abort (default 300s for HTTP-transport MCP
servers), which no launcher raises. A wait near the 900s recommendation was
silently killed at ~300s in any session, launcher or not.
WaitForTaskChange now reports MCP progress every 30s while polling, which
resets that idle timer. Verified against a real claude -p call (no launcher
env overrides) surviving a 341s wait via 60s pings -- the same silence that
previously aborted at 300s. Tool description and the list-handler prompt no
longer claim ClaudeDo's launchers guarantee a long wait survives.
batch_get_tasks with includeDescription=true had no size guard: 8 tasks'
full Description/Result serialized to a single 51k-char line, blowing past
the tool-result token budget and forcing a file/PowerShell workaround to
read it back. Roadblock text was also only reachable via a second get_task
call per task, since roadblockCount (in the lean ref) has no text sibling.
- descriptionMaxChars (default 1500, was unlimited) truncates Description
and Result independently, flagged via *Truncated/*FullLength so a caller
never silently works off a cut string.
- fields narrows taskFull to just the named properties, including the new
roadblockText (the tail of Result after TaskRunner's roadblock marker) —
reachable without pulling in the rest of Result/Description.
- taskFull is now BatchTaskDetailDto, a batch-only shape decoupled from
TaskDto so get_task's own contract is untouched.
- The whole response is capped (MaxResponseChars); over that, the call
throws naming which parameter (descriptionMaxChars/fields/taskIds) to
adjust instead of shipping an oversized payload.
10 tasks with 5000-char descriptions (default settings) serialize to
~13.8k chars, comfortably under the 25k-char cap.
Omitted fields on set_task_config/set_list_config used to be indistinguishable
from an explicit clear, so any single-field update silently wiped every other
override (including SessionSkills, which wasn't even a tool parameter). Now
only the fields you pass are changed; clearing an override requires naming it
in the new clearFields array (a sentinel string doesn't work uniformly since
maxTurns is an int). set_list_config's "all four null deletes the config" case
is preserved but only reachable via clearFields. Return values now reflect the
full resulting config.
get_merge_conflicts and resolve_conflict_hunk let an MCP caller resolve a
leaveConflictsInTree merge hunk-by-hunk (ours/theirs/base/literal text)
instead of grep-parsing diff3 markers itself. Reuses the existing
TaskMergeService/ConflictMarkerParser machinery (adds MergeSegment.StartLine
and a non-staging TaskMergeService.WriteConflictFileAsync) rather than a
second parser. A partially resolved file is written without git add so
continue_merge keeps refusing while any hunk still has markers.
MergeHelperSelectionModalViewModel now loads ParentTaskId and orders
rows hierarchy-aware (parent immediately followed by its children,
mirroring TasksIslandViewModel.Regroup). Children render indented with
the same indent-track visual as the main task list; ticking a parent
cascades selection to its children, while a child can still be
deselected on its own. A child whose parent got filtered out (e.g.
already Done) renders top-level with an explicit hint instead of
disappearing. Confirm keeps returning parents ahead of their children.
The diff viewer used ShowDialog at both call sites (task details, worktrees
overview), blocking navigation of the rest of the app while open. Switch both
to Window.Show(owner) and reuse the one open window (swap DataContext,
Activate()) instead of stacking a second one on re-open.
Add an opt-in maximize/restore button + double-click-titlebar toggle to
ModalShell (ShowMaximizeButton, default false so other modals are untouched),
wired on for DiffViewerView only. Reuses the existing Icon.WinMax/WinRestore
geometry and OffScreenMargin inset from MainWindow's own maximize handling.
The review gate (Approve & Merge locked until the diff is opened) already
fires DiffViewed before the dialog/window is shown, so it is unaffected by
the modal-to-non-modal change.
get_task_diff gained an optional paths param (git pathspec, both stat and
full-diff modes) so a large multi-file task can be narrowed to the files
actually in question instead of shipping the whole diff.
preview_merge_set now also reports Subsets: pairs where one task's changed
files are a proper subset of another's in the same set, the strongest
available post-hoc signal that a task may be redundant with another.
Headless Avalonia ListBox+VirtualizingStackPanel spike answers all five
questions from the Phase 2a gate task: containers stay bounded at 1000
items, the existing InputHitTest ghost-drag model survives recycling,
variable row heights don't drift the Extent (only naive index*height
math would), auto-scroll via Offset mutation is mechanically feasible,
and mixed header/task rows template correctly via implicit DataTemplates.
Recommendation: proceed to Phase 2b. Spike code itself is throwaway and
not checked in.
git add -A ran against the shared target checkout, sweeping in
whatever untracked or unrelated modified files a concurrent session
left there and committing them into an unrelated merge. Stage exactly
the paths that were reported conflicted and resolved instead.
A hand-driven task produces no streamed agent output, so the Output tab
opened empty. Bind now selects git for manual/interactive rows and resets
to output for autonomous ones.
DiffEditorSetup is the place for AvaloniaEdit host boilerplate now, so note why
the diff viewer and the merge editor share it but stay separate controls, and
that ModalShell owns the titlebar drag and the OffScreenMargin inset for every
modal.
ModalShell moved its window by assigning Window.Position on every pointer-move,
which bypasses the OS move loop entirely - so Windows never saw a drag gesture
and no snap was ever recognised. Dragging the diff viewer to the top edge did
nothing. The titlebar now calls Window.BeginMoveDrag, which hands the drag to the
window manager and brings back maximise-on-top-edge, half-screen side snap, snap
layouts and the preview overlay. No opt-in flag is needed: the OS only snaps
resizable windows, so the non-resizable modals stay as they are.
The stale comment claiming BeginMoveDrag does not work under Avalonia 12 was
wrong - the method is there in 12.0.4. The VisualRoot cast was the actual problem,
and the window still has to come from TopLevel.GetTopLevel.
Also inset the shell by the window's OffScreenMargin, mirroring what MainWindow
already does for itself. The modals share MainWindow's extended-client-area flags,
so without it a maximised modal overhangs the screen by the invisible resize
border and gets its edges and close button clipped.
Three things were wrong with the fresh side-by-side viewer:
- The panes scrolled independently. The sync hunted the editors' templated
ScrollViewers at load time, but the viewer starts collapsed (no file selected),
so nothing was ever measured, no template existed, and the hook silently
no-opped for the lifetime of the window. Reading now goes through
TextView.ScrollOffsetChanged, which exists from construction; writing goes
through a lazily resolved ScrollViewer.Offset. TextEditor.ScrollToVerticalOffset
is NOT usable here - it is a silent no-op in AvaloniaEdit 12.0.0 even with the
editor templated (verified headlessly).
- Neither pane said what it showed. Added a BASE | WORKTREE header row inside
DiffTextView, sharing the pane grid's columns so each label sits over its editor.
- The layout picker was two lookalike ToggleButtons. It is now a segmented switch
(Border.segmented + Button.segment.active), with wrap demoted to an icon toggle
since it is orthogonal to the layout mode.
The scroll sync, TextMate setup, grammar switching, brush fallback and segment
struct were duplicated between this control and the 3-pane conflict resolver,
which the design doc had used as a copy-paste template. They now live in
DiffEditorSetup; both surfaces migrated. They stay separate controls on purpose -
a read-only two-way renderer over aligned rows with filler lines is not a variant
of a three-way editor over a writable document.
The resolver thereby also picks up the fixed scroll sync and the TryFindResource
brush lookup (its TryGetResource never resolved anything). All four merge tokens
happen to equal their hardcoded fallbacks, so nothing changes on screen.
QueueServiceSlotFailureTests's throwing-slot test broke its poll loop the
instant the DB read observed Status==Failed, but TaskStateService.FailAsync
commits the status flip before calling the broadcaster's TaskUpdated, so the
assertion could race ahead of the broadcast landing in hub.Proxy.Calls
(~1-in-5 failures in isolation). Wait for both signals before breaking.