47ff0804a5cd53b375d179d2ab04c660880937c0
856 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
47ff0804a5 |
feat(cli): make --port the user-facing port flag (#737)
* feat(cli): add --port flag as the user-facing port knob
Introduces --port (and KANDEV_PORT) as the primary, command-aware port
flag: in start/run it sets the Go backend port (the proxy front door
the browser actually hits), and in dev it sets the Next dev server port.
--backend-port and --web-port still work but emit a one-time stderr
deprecation note pointing at --port and --web-internal-port respectively.
* feat(cli): print labelled "open:" URL on startup
Replaces the ambiguous "[kandev] url:" / "ready at" lines with a
labelled block that always shows the user-facing URL first, plus the
backend (api) and web (internal) URLs when they differ. Each command
now declares its own publicUrl: backend in start/run (where the Go
server reverse-proxies Next.js), web in dev (browser hits Next direct).
* style(cli): apply prettier formatting to args.test.ts
* refactor(cli): tighten arg parsing per review feedback
- Validate value-taking flags (--port, --backend-port, --web-internal-port,
--web-port, --version) and reject missing/non-numeric values with a clear
ParseError instead of leaking NaN into the port validator.
- Drop the test-only _resetWarnedFlagsForTest export and the module-level
warned-flag set; parseArgs now returns deduped deprecatedFlags and cli.ts
emits warnings command-aware (e.g. --backend-port → KANDEV_BACKEND_PORT
in dev, where --port maps to web).
- Stop double-printing the "open:" URL: logStartupInfo now lists labelled
backend (api) / web (internal) URLs only, leaving the post-readiness
open: line as the single source of truth.
- Fix help text: --web-internal-port works in dev too.
- Drop multi-line JSDoc blocks per CLAUDE.md style guide.
* fix(cli): validate env-var ports and collapse comment to one-liner
Catches non-numeric KANDEV_PORT / KANDEV_BACKEND_PORT / KANDEV_WEB_PORT in
envPort with a clear ParseError instead of letting NaN reach
ensureValidPort. Drops a multi-line comment per CLAUDE.md style.
* fix(cli): reject non-integer port values
Number.isFinite accepts floats like 3000.5 which then fail downstream
in ensureValidPort with a generic message. Tighten parsePort and
envPort to require integers and add float tests for both flag and
env-var paths.
* fix(cli): reject out-of-range port values with flag-named errors
Adds 1-65535 range checks to parsePort and envPort so values like 0,
-1, or 65536 fail with a flag/env-var-named ParseError instead of
reaching ensureValidPort with a generic 'backend port' label.
* test(cli): cover KANDEV_WEB_PORT happy path and range validation
* test(cli): cover KANDEV_WEB_PORT in run/start mode too
* fix(cli): explicit CLI flag wins over env var for port resolution
Previously KANDEV_BACKEND_PORT silently overrode --port because each
process port resolved its own env var before the generic --port
fallback. New precedence per process: explicit CLI flag > generic
--port flag > explicit env var > generic KANDEV_PORT. Also relabel
the startup info from "backend (api)" / "web (internal)" to plain
"backend" / "web" — "internal" was misleading in dev where the web
URL is what users open.
* style(cli): collapse resolvePorts comment to one line
* fix(cli): reject empty --version= and empty port env vars
- --version= (no value after =) now throws ParseError instead of
silently storing the empty string as the version tag.
- envPort distinguishes unset (undefined) from empty (""); an empty
KANDEV_PORT / KANDEV_BACKEND_PORT / KANDEV_WEB_PORT is now treated
as misconfiguration and fails fast.
* refactor(cli): make --port a plain alias for --backend-port
Drops the command-aware split where --port mapped to web in dev and
backend in run/start. --port and --backend-port now both set
options.backendPort regardless of command, and KANDEV_PORT is a plain
fallback for KANDEV_BACKEND_PORT. The whole point of the original
fix was that the *backend port* is the URL users actually open in
run/start (because the Go server reverse-proxies Next.js); a single
flag that always means "the backend port" matches that mental model
without per-command surprises.
Removes deprecationReplacement and the command-aware warning logic
since the two flags are now interchangeable. --web-port → --web-internal-port
deprecation stays.
* test(cli): re-add KANDEV_WEB_PORT run-mode coverage dropped in refactor
|
||
|
|
3d93d9e93c | fix(mcp): improve create_task_kandev repository and workspace resolution (#733) | ||
|
|
183efad419 | feat: expose MCP server to external coding agents (#732) | ||
|
|
8fb2e5ba52 | fix(lifecycle): publish agentctl events on workspace restore (#731) | ||
|
|
4277ef495a | fix(web): self-recover dockview layout from corrupt persisted state (#729) | ||
|
|
c7274e84c1 |
fix(agentctl): populate diff for renamed-and-modified files in git status (#730)
* fix(agentctl): populate diff for renamed-and-modified files in git status The numstat output uses notation like `old.txt => new.txt` for renamed files, but the Files map is keyed only by the new path. Resolve numstat paths to the new path before map lookup so RM file diffs are populated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(agentctl): cover filename-starts-with-brace rename case Adds a regression test for `{{page => layout}}.svelte` to lock in that LastIndex on `{` correctly picks git's brace block over the filename's leading literal `{`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
51cc14d028 |
refactor(worktree): drop unused flat-layout worktrees/ path (#708)
* refactor(worktree): drop unused flat-layout worktrees/ path
Every worktree creation now routes through the task-dir layout
(~/.kandev/tasks/{title}_{suffix}/{repo}/); the legacy flat layout
under ~/.kandev/worktrees/ has been unreachable since the subtasks
PR populated TaskDirName/RepoName unconditionally. Removes the dead
Config.BasePath, Manager.createWorktree, and the flat reconciler.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(worktree): remove unused Reconcile, add guard regression test
Address PR review feedback:
- Reconcile (and reconcileTasksDir / isOrphanedTaskDir) had no callers
anywhere in the tree after the flat-layout removal. Deleted rather than
left dangling; if startup orphan cleanup is wired up later it can return
alongside its actual call site. Also moots the stale doc comment that
CodeRabbit flagged.
- Add TestCreate_MissingTaskDirFields_ReturnsErrTaskDirRequired covering
the three permutations (missing RepoName, missing TaskDirName, both
missing) so a regression in the ErrTaskDirRequired guard surfaces in
CI rather than silently.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
ab16b03fa0 | fix(web): preserve chat scroll position when maximizing panel (#728) | ||
|
|
8694de20ea | fix(web): prevent crowding in narrow changes panel and dockview tabs (#727) | ||
|
|
2416b8dd49 |
fix(orchestrator): drain queued message on workflow transition to non-auto-start step (#726)
* fix(orchestrator): drain queued message on workflow transition to non-auto-start step A workflow transition (e.g. In Progress → Review) made handleAgentReady early-return before draining the message queue, on the assumption that the new step's auto_start_agent would handle the next prompt (#677). For steps without auto_start_agent (Review, etc.) nothing ever drained the queue and user-queued messages stuck forever. Add drainQueuedMessageAfterTransition and call it from the two processOnEnter paths that don't auto-start the agent (no on_enter actions, and the default switch case). Restores pre-#677 behavior for non-auto-start transitions while keeping the deadlock fix for auto-start steps intact. Includes regression test covering the no-on_enter-actions case. * fix(orchestrator): address PR review feedback on queue drain - Log a Warn when drainQueuedMessageAfterTransition discards an empty queued message, matching handleAgentReady's behavior. (Claude) - Fix data race on mockAgentManager.capturedPrompts surfaced by `-race`: PromptAgent now appends under the existing mu, and adds an opt-in promptDone channel so tests can wait deterministically. (Claude) - Replace sleep-based polling in the new regression test with a select on promptDone — matches the project's no-time.Sleep guideline. (Claude, CodeRabbit) - Drain the queue in the sessionSwitched + step.Prompt error handler goroutine in processOnEnter — same orphan-queue class of bug as the rest of this PR, just on a different no-auto-start path. (Greptile P1) --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
fbe2410fdb |
feat(web): hide Jira buttons when disabled or auth failing (#725)
* feat(web): hide Jira buttons when disabled or auth failing
Hide the Jira import button on the task creation dialog and the "Link Jira" button on the task top bar when the workspace toggle is off, no Jira config exists, or the most recent backend auth probe failed. Centralizes the gating logic in a shared useJiraAvailable hook.
* fix(web): address PR review feedback on Jira availability gate
- Gate the auth probe on `loaded` from useJiraEnabled so it never fires when the toggle starts disabled
- Reset cached auth on workspace change so the previous workspace's `true` can't briefly leak into the new one
- JiraTopbarButton now consumes the shared useJiraAvailable hook instead of replicating the two-step gate inline
* fix(web): apply prettier formatting to use-jira-availability.ts
* fix(web): tag Jira auth state with workspaceId to avoid poll-tick flicker
Storing `{workspaceId, authed}` together and filtering at read time invalidates stale auth synchronously when the workspace switches, removing the need for a setAuthed(false) reset that was previously firing on every 90s poll tick. Adds a fake-timer test that asserts no false render slips through across a poll tick while auth stays healthy.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
|
||
|
|
2e2bd441c6 |
fix(passthrough): apply profile cli_flags to passthrough command (#722)
* fix(passthrough): apply profile cli_flags to passthrough command PassthroughOptions had no CLIFlagTokens field, so the passthrough launch path silently dropped profile-configured cli_flags. Mirror the ACP launch path: resolve via cliflags.Resolve and append the tokens through StandardPassthrough.BuildPassthroughCommand. Fixes #718. * fix(passthrough): skip auto-restart on fast-fail launch errors A bad CLI flag, missing binary, or auth failure makes the agent process exit immediately. The auto-restart loop then thrashed forever, spamming "[Agent exited. Restarting...]" without ever recovering. Track the start time of each passthrough process and short-circuit the restart path when an exit lands within 2s with a non-zero code. Surface a one-shot terminal banner pointing the user at the likely fix instead. * refactor(passthrough): snapshot start time at call site to avoid race Claude review on PR #722: handlePassthroughExit ran in a goroutine and read execution.PassthroughStartedAt while the next launch could concurrently write it. Snapshot the value synchronously in handlePassthroughStatus and thread it through; isFastFailExit now takes the start time directly. Also extracts attemptPassthroughRestart and notifyFastFailExit to keep handlePassthroughExit under the funlen limit. * refactor(passthrough): measure fast-fail uptime from status timestamp Claude review on PR #722: handlePassthroughExit's uptime measurement folded in the cleanupDelay sleep (~100 ms), so the logged duration was systematically inflated and a process that died at, say, 1.95 s would slip past the 2 s cutoff. Use status.Timestamp (set when the child actually exited) as the exit time and pass it through to isFastFailExit. notifyFastFailExit now receives a pre-computed uptime so the log reflects real child runtime. |
||
|
|
5cfcfb437e | feat(web): add GitHub dashboard shortcut to command panel (#721) | ||
|
|
6b35b54a51 | fix: restore release changelog boundaries (#716) | ||
|
|
7f0fe7de1f |
release: app v0.38, cli 0.16.0
release / build-web (push) Has been cancelled
release / docker (push) Has been cancelled
release / build-bundles (aarch64-linux-gnu-gcc, arm64, linux, ubuntu-latest, linux-arm64) (push) Has been cancelled
release / build-bundles (amd64, darwin, macos-14, macos-x64) (push) Has been cancelled
release / build-bundles (amd64, linux, ubuntu-latest, linux-x64) (push) Has been cancelled
release / build-bundles (arm64, darwin, macos-14, macos-arm64) (push) Has been cancelled
release / build-bundles (x86_64-w64-mingw32-gcc, amd64, windows, ubuntu-latest, windows-x64) (push) Has been cancelled
release / publish-release (push) Has been cancelled
|
||
|
|
2a3df653f7 |
feat(filters): add 'Has PR' task sidebar filter (#713)
Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
d961d1b430 | fix(web): scope settings workflow list to current workspace (#714) | ||
|
|
8bf97911d5 |
fix(orchestrator): plug zombie turn leak pinning sessions to RUNNING (#712)
* fix(orchestrator): plug zombie turn leak pinning sessions to RUNNING Two independent code paths lazily created turns without sharing state. service.CreateMessage (DB-based) created a turn for inbound user messages; PromptTask then unconditionally created another via startTurnForSession (in-memory + DB). The first turn was never tracked by the orchestrator, so its completed_at never got set and the session state machine kept the session pinned to RUNNING forever — the "agent is processing" spinner that wouldn't clear. - startTurnForSession is now idempotent: adopts an existing open turn from the in-memory map or the DB before creating a new one. - completeTurnForSession is now DB-authoritative: the in-memory map is a hint, the DB is truth. Loops to mop up multiple pre-existing zombies with a sanity bound. - CancelAgent captures the turn ID before agentManager.CancelAgent so the cancel message can't race the agent's complete event into a phantom turn. - service.GetActiveTurn returns (nil, nil) for "no active turn" instead of leaking sql.ErrNoRows. Adds five regression tests covering adoption, idempotency, untracked close, multi-zombie sweep, and double-complete idempotency. * fix(orchestrator): address PR review feedback on turn lifecycle - Log DB read errors in startTurnForSession adoption path so transient failures aren't silently swallowed (Claude, CodeRabbit, Greptile). - completeTurnForSession probes once after the iteration cap and only warns when a turn is *still* observed — closing exactly maxIterations is no longer a false alarm (CodeRabbit). - Clarify the CompleteTurn-failure log: returning early defers cleanup to the next sweep rather than silently dropping zombies (Greptile). - Route getOrStartTurn through GetActiveTurn so ErrNoRows normalization is the single contract (Claude). Adds TestCompleteTurnRespectsIterationCap to lock the cap behavior. * fix(orchestrator): align getOrStartTurn observability with adoption path Log DB read errors in getOrStartTurn before falling through to StartTurn — same observability contract as the orchestrator's startTurnForSession (Claude review on the previous fix-up). Also add a comment in turn_lifecycle_test.go explaining the hardcoded "task1" TaskID in repoTurnService matches seedSession, so future test authors reusing the helper know the coupling. --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
b805bf796a | fix(settings): toggle off default-on curated CLI flags (#711) | ||
|
|
12bd5b2cf3 |
feat: plan checkpointing with rewind UI (#694)
* feat(backend): plan revision history with checkpoint/revert
Persist every plan edit as an immutable TaskPlanRevision with author
attribution (agent vs user) and expose WS actions to list, fetch, and
revert to past revisions. Emits task_plan.revision.created and
task_plan.reverted events so the UI can react in real time.
* feat(web): plan revision history UI with rewind to past versions
Add a rewind button to the plan panel that opens a revisions list
showing each checkpoint with author (agent name or user) and timestamp,
and lets the user restore any past revision as the new head. Plan
state, hooks, and WS handlers are extended to load, cache, and react
to revision-created/reverted events.
* test(web): e2e coverage for plan checkpointing and rewind
Cover the full revision flow: plan edit creates a checkpoint,
the revisions panel lists authors and timestamps, and reverting
restores the selected revision as the new head. Extends session
page object and backend fixtures with revision-related helpers.
* fix(web): stabilize plan revisions state and avoid re-render loop
Zustand selector returned a fresh [] for missing revisions, fanning out
re-renders that tripped React's infinite-loop guard when the plan panel
mounted. Swap to a frozen empty-array sentinel, auto-load revisions on
mount so WS events fired before connect aren't lost, and gate the
popover's on-open fetch so reopening doesn't re-sort rows mid-click.
* test(web): make plan checkpointing e2e resilient to agent auto-layout
Agent-authored plan writes auto-open the plan panel and can push the
chat tab inactive, so the chat idle placeholder isn't always visible
once the agent finishes. Accept either chat or plan panel as the idle
signal, make openPlanPanel and openRewind idempotent, reopen the
popover after a revert cancel (the confirm dialog closes it), and drop
the toast assertion since no Toaster is mounted in the app.
* fix(backend): atomic plan revision write + review polish
- Add `WritePlanRevision(ctx, head, rev, coalesceLatestID)` repository method
that wraps HEAD upsert + MAX+1 compute + insert/merge in a single write tx,
closing the TOCTOU race on `revision_number` and partial-failure divergence
between HEAD and history. `upsertPlan` and `RevertPlan` both route through it.
- Add `ErrRevisionIDRequired`; return it for empty `TargetRevisionID` (was
previously conflated with `ErrRevisionNotFound`). Handler maps to validation.
- Backfill fallback `author_kind` changed `"user"` → `"agent"` to match
`CreateTaskPlan` and the column default.
- Extract shared `revisionSelectCols` constant; rename `max` local (shadowed
Go builtin) and `dto` local (shadowed package name).
* fix(web): address plan-revisions review feedback
- `mcpWrite` uses `JSON.stringify(content).slice(1, -1)` to escape backslashes,
quotes, and control chars in one pass — closes the CodeQL finding.
- `clearTaskPlan` now evicts `revisionContentCache` entries for the task's
revisions before dropping the maps, preventing orphaned cache growth.
- `RevertConfirmDialog` wraps `onRevert` in try/catch/finally so rejections
surface a toast and the dialog always dismisses.
- Hoist `TaskPlanRevision` import in `task-plan-panel.tsx`.
- `RevisionRow` derives relative time from props every render with a 30s tick,
so prop changes refresh immediately.
- Strengthen author-switch e2e with `.not.toBeEmpty()` to also reject empty or
truncated renders.
* fix(web): evict revision content cache on coalesced update
Coalesced writes update an existing revision's content on the backend, but
the WS `upsertPlanRevision` payload carries only metadata. Without eviction,
a cached preview of the revision stays stale after the agent re-writes the
plan within the coalesce window.
* fix(web): plan history UI polish from local testing
- Drop the redundant "Plan" title in the panel header bar; the dockview/mobile
tab already labels the panel.
- Author display: show "You" with the user icon for user-authored revisions,
and the agent profile name with the AgentLogo (resolved from the active
session's `agent_profile_snapshot.agent_name`) for agent revisions, matching
the icon used elsewhere in the app.
- Replace ScrollArea with a plain `max-h-96 overflow-y-auto` div so the
popover stays bounded when revision counts grow past ~7 rows.
- Drop the em dash from the revert dialog description.
* feat(web): add lineDiff helper for plan revision compare
Tiny wrapper around the `diff` package that classifies each line as add /
remove / context and strips the trailing-newline phantom entry. Also exposes
`diffSummary` for the dialog header counts. Pure functions; backed by Vitest.
* feat(web): plan revision preview + compare state
Adds two task-scoped store keys for the new dialogs:
- `previewRevisionIdByTaskId` — at most one open preview per task; cleared on
`clearTaskPlan`.
- `comparePairByTaskId` — ordered 2-slot pair. `toggleComparePair` reuses a
`nextPair` helper that handles unselect (already-picked id), fill (open
slot), and FIFO eviction (full pair). `clearTaskPlan` resets it too.
* feat(web): plan revision preview + diff dialogs
- `useTaskPlan` exposes `previewRevisionId`, `setPreviewRevision`,
`comparePair`, `toggleCompareSelection`, `clearComparePair` (extracted into
`usePreviewCompareState` to keep the surrounding hook within the size cap).
- New `PlanRevisionPreviewDialog`: read-only modal for a single revision with
Restore + "Compare with current" CTAs. Restore routes through the existing
RevertConfirmDialog so the confirmation copy stays consistent.
- New `PlanRevisionDiffDialog`: line-level unified diff between two revisions
with adds/removes highlighted; orders the pair by revision_number so the
older one is always "before"; Restore targets the older side.
- Popover row split into a clickable body (opens preview), a Compare toggle
(icon button per row), and the existing Revert button. Compare chips appear
at the top of the popover with a Diff CTA when both slots are filled.
- Both dialogs are remounted via key on revision/pair change so loader state
resets without ESLint-flagged setState-in-effect resets.
* test(web): e2e coverage for plan preview and compare-diff dialogs
Extends the SessionPage POM with locators for the new preview and diff
dialogs (chips, compare toggle, restore buttons, body + summary). Adds two
scenarios in plan-checkpointing.spec.ts:
- Click a revision row body → preview shows the right content; the Restore
CTA opens the existing confirm dialog and the revert lands.
- Toggle compare on two revisions → diff opens with v1 → v2 ordering and
shows at least one add and one remove line; the Restore CTA targets the
older side.
* feat(web): render markdown in plan preview + diff dialogs
Adds a shared `PlanReadOnlyMarkdown` Tiptap renderer that reuses the same
StarterKit + tiptap-markdown + tables/tasks/code-block extensions as the live
plan editor (minus interactive bits — slash menu, drag handles, comment
marks). Wired into both dialogs:
- Preview replaces the raw `<pre>` block with the rendered Tiptap view.
- Diff dialog now stacks two rendered sides (Before / After) with red/green
tinted borders, kept above the existing line-level breakdown so the user
gets both rendered comparison and an exact textual change list.
Tiptap doesn't ship a free diff/track-changes extension (it's part of Tiptap
Pro), so the side-by-side rendering plus the line-level list together stand
in for that.
* fix(web): plan diff dialog — text-only with unified/split modes
Following local feedback:
- Drop the rendered-markdown side-by-side panes from the diff dialog. Diff
view is text-only again, like a code review.
- Add a Unified / Split mode toggle. Split mode aligns each remove with the
adjacent add on the same row (single-line edits stay paired) and stripes
empty cells where one side has nothing.
- Fix the highlight-truncation bug: long lines used to lose their tinted
background past the viewport on horizontal scroll because the row width
was capped to the visible area. Rows now use `min-w-full w-max` so the
background extends through the full scroll width.
Preview dialog keeps the rendered Tiptap markdown — only the diff reverted
to plain text.
E2E covers both modes and the cell kinds.
* feat(web): formatPreciseTime util for minute-level timestamps
Returns 'just now', 'Xs ago', 'Xm ago' for the first hour, then 'Today, HH:MM',
'Yesterday, HH:MM', or '{Mon} {DD}[, YYYY], HH:MM' for older entries — keeps
exact minute-of-day visible instead of decaying to 'yesterday'/'2d ago'.
* fix(web): render markdown via setContent in plan read-only renderer
The tiptap-markdown extension's parser only fires on `setContent` calls, not
on the initial `content` option. Passing `content: value` to `useEditor` was
treating the markdown source as raw text, so the preview dialog rendered
unparsed markdown.
Drop the `content` option and instead call
`editor.commands.setContent(content, { emitUpdate: false })` from a useEffect
once the editor is created — which is what the live plan editor effectively
relies on through tiptap-markdown's setContent override.
* fix(web): plan history UX — bigger modals, split layout, simpler compare
- Bump dialog widths: preview now `max-w-5xl w-[92vw]`, diff
`max-w-6xl w-[95vw]`, both with taller max-height. Plenty of room for
rendered markdown and side-by-side diffs.
- Replace the table-based split layout with a CSS grid (`grid-cols-2 w-max`).
The table-fixed cells were overflowing into each other when content was
wider than half the dialog; the grid lets the natural width drive a single
horizontal scrollbar on the parent and keeps the two columns properly
separated.
- Switch row timestamps to `formatPreciseTime` so users see minute-of-day
("Today, 14:32", "Yesterday, 09:14", "Apr 21, 11:08") instead of decaying
to "yesterday".
- Simplify the compare flow: drop the per-row Compare toggle, the chip strip
at the top of the popover, and the standalone "Diff" button. Compare is
now a side effect of preview — open any version, then click "Compare with
previous" or "Compare with current". E2E test updated to use the new path.
* test(web): update plan checkpointing e2e for the new compare flow
Drops the locators for the row Compare toggle, chips, and standalone Diff
button (those UI bits are gone). Adds a locator for the new
"Compare with previous" preview button. The compare scenario now opens the
diff via preview → "Compare with current" instead of the old chip flow.
* fix(web): preview renderer reuses live editor's markdown styles
`globals.css` keeps heading sizes / list spacing / code-block padding under
.markdown-body and .tiptap-plan-wrapper, and the live editor opts in by
applying both classes (plus a conditional .dark for theming). The read-only
renderer was using `prose prose-sm` instead, which doesn't actually take
effect on the .ProseMirror node — leaving everything the same size.
Switch the read-only renderer to the same class set, follow the live
editor's `useTheme` -> `dark` pattern, override `!h-auto` so the renderer
sizes to its content, and trim the editor's default 0.75rem padding inside
the dialog body.
* fix(web): split diff has fixed width with synced horizontal scroll
The previous split layout grew the whole dialog wider when content overflowed
which felt wrong. Now:
- The dialog stays at its fixed width.
- Each pane has its own `overflow-x-auto` so each side is capped at half the
dialog and gets its own horizontal scrollbar.
- A scroll listener keeps both panes' `scrollLeft` in lockstep (with a
re-entrancy guard so the bounce-back doesn't form a loop) — corresponding
rows stay aligned visually no matter which side the user scrolls from.
- The diff body's overflow-x is hidden in split mode (the panes own it) and
only overflow-y stays on the body so vertical scroll covers both panes
at once.
* feat(backend): resolve agent profile name from session for plan revisions
The MCP-side handlers (handleCreateTaskPlan / handleUpdateTaskPlan) don't
carry the agent's display name in the request, so without a lookup every
agent-authored revision was rendering as the literal "Agent" fallback in
the history UI.
PlanService now looks up the task's session and reads the agent profile
display name from `agent_profile_snapshot` (preferring "label", then
"name", then "agent_display_name") when the request is an agent write
without an explicit author_name. Active session is tried first; falls
back to the most recent session by started_at so plans written between
turns still get the right author name. If no usable session/snapshot
exists, the existing "Agent" fallback applies.
The repo dependency type is widened from `repository.PlanRepository` to a
small private `planRepo` interface that adds the two session lookups —
the SQLite repository (`*sqliterepo.Repository`) already implements both,
so no caller wiring changes.
* fix(web): plan history popover row + restore button polish
- Popover content's default `gap-4` between flex children (from
@kandev/ui's PopoverContent) was rendering as visible space above the
top-most row. Override with `gap-0` so the list sits flush against the
header.
- Switch row layout from `items-start` to `items-center` so the
Restore button stays vertically centered against the row body even
when the body grows taller (timestamp + restored-from marker on revert
rows).
- Rename the per-row "Revert" button to "Restore" and prefix it with
IconRestore so it matches the dialog wording and the restored-from
marker icon. The data-testid stays `plan-revision-revert-button` for
e2e selector stability.
* fix(backend): plan revision review feedback + lint
- `wsGetTaskPlanRevision` now returns a 400 validation error when
`revision_id` is empty, matching `wsRevertTaskPlan`'s
`ErrRevisionIDRequired` branch (was silently mapped to 404 via the
service's lookup miss).
- `agentDisplayNameFromSnapshot` tries `name` first, then `label`, then
`agent_display_name`. The orchestrator's canonical key wins over any
stale older `label` that may still be in a session snapshot.
- Add `idx_task_plan_revisions_task_number` on
`(task_id, revision_number DESC)`. `GetLatestTaskPlanRevision` runs on
every plan write (coalesce check); the existing `(task_id, created_at)`
index doesn't help since these queries order by `revision_number`.
- Drop `Deprecated:` from `NextTaskPlanRevisionNumber`'s doc comment so
staticcheck stops flagging the existing test usage. The TOCTOU caveat
stays in the prose.
* fix(web): plan revision review feedback, prettier, e2e resilience
- `loadRevisionContent` reads `revisionContentCache` lazily through the
store API inside the callback, so its identity stays stable. Selecting
the cache map at the hook level was recreating the callback whenever
any task's content was cached, retriggering downstream content-fetch
effects.
- `task.plan.reverted` WS event becomes a no-op handler.
`task.plan.revision.created` fires for the same row first and already
upserts; the duplicate handler was double-evicting the
`revisionContentCache` entry and triggering an extra Zustand update.
- e2e: agent author assertion now uses `not.toHaveText("You")` +
`not.toBeEmpty()` instead of the literal `"Agent"` (which only
matched the legacy fallback — agent rows now resolve to the profile
display name).
- e2e: `openPlanPanel` retries the toggle a few times after a page
reload — the panel toggle can race hydration and silently no-op,
which broke the persistence test.
- Apply prettier to the files flagged by the formatting check.
* fix(plan): toast on silent revert failure + revision ownership check
- task-plan-revisions: when `onRevert` (the hook's `revertTo`) swallows
the error and returns null, the dialog used to close silently with no
feedback. Add an `else` branch that fires `toast.error("Failed to
restore plan")`.
- wsGetTaskPlanRevision: accept an optional `task_id` and reject with a
400 "Revision does not belong to task" when the loaded revision's
task doesn't match. Mirrors `wsRevertTaskPlan`'s ownership check so a
WS client can't read content for an arbitrary revision UUID outside
the tasks it already holds a reference to.
- plan-api `getPlanRevision` takes optional `taskId`; `loadRevisionContent`
threads the active task id so the check is exercised on the happy path.
|
||
|
|
8948ebdb25 | fix(acp): flush ScheduleWakeup output via synthetic prompt (#706) | ||
|
|
5644b0ad18 | feat(preview): add PR preview environments via Sprites (#707) | ||
|
|
ec9637c517 | feat: opt-in fresh-branch checkout for local executor task creation (#695) | ||
|
|
54b98ed9eb |
feat(jira): add Jira integration for ticket browsing, import, and task linking (#705)
* feat(jira): add Jira integration for ticket browsing, import, and task linking Adds a workspace-scoped Atlassian Cloud integration: per-workspace settings (site URL, email, API token or session cookie), encrypted secret storage, and a REST client for tickets, projects, search, and transitions. A background poller probes credentials on a 90s cadence and the keepalive runs immediately on save so the UI's connected indicator updates without delay. The /jira page provides ticket browsing with filter pills, custom JQL, saved views (including JQL), and configurable task-launch presets that prefill TaskCreateDialog. JiraLinkButton on the task top bar links an existing task to a ticket by prepending its key to the title; the ticket popover surfaces details, transitions, and re-auth prompts on session expiry. Topbar shows separate icon buttons for GitHub and Jira when each is configured. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): address PR review feedback - Token-based pagination (pageToken/isLast) replaces broken offset/total pagination - Race-fix in service.clientFor: re-check cache after acquiring write lock - sync.Mutex guards poller started/cancel state against concurrent Start/Stop - Channel-based + atomic.Int32 test sync replaces time.Sleep polling in poller_test - Async auth probe on SetConfig with detached context.Background goroutine - Distinguish 4xx vs 5xx in httpSetConfig via ErrInvalidConfig sentinel - Propagate 4xx APIErrors from ListTransitions in GetTicket - Distinguish "not found" from real errors in jiraSecretAdapter.Exists - Log secret Exists errors instead of swallowing - walkADF: handle softBreak (was only hardBreak despite the doc-comment) - Consolidate JIRA_KEY_RE in jira-ticket-common; remove duplicates - ticket-row imports formatRelative/statusBadgeClass from common - isFilterState type guard validates SavedView.filters shape - task-presets readStorage preserves user's "cleared all" intent (returns []) - task-presets-section gates draft sync on `loaded` to avoid wiping in-progress edits - jira-api: caller `init` no longer overrides method/body via spread order - jira-settings: disable Test button when no saved/typed secret Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): record auth probe failure even when probe ctx expires - RecordAuthHealth now uses a fresh context.Background-derived ctx with a 5s timeout for the UpdateAuthHealth DB write, so a probe that consumes its full 15s deadline still persists the failure (greptile P1). - SetConfig goroutine drops its redundant outer timeout — RecordAuthHealth manages its own probe timeout, so context.Background is enough. - Add Service.SetProbeHook so tests can synchronize on probe completion; service_test waitForAuthProbe now blocks on the hook channel instead of time.Sleep polling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): address coderabbit review feedback (round 3) - url.PathEscape ticketKey in GetTicket/ListTransitions/DoTransition paths to harden against malicious keys ('../../foo', 'KEY?override=x'). - factoryHit on the test fixture is now sync/atomic.Int32; the async probe goroutine spawned by SetConfig races the test thread under -race. - GetConfig logs Reveal failures (was silently dropped, leaving the UI to show "no expiry info" indistinguishably from a freshly-saved cookie). - DeleteConfig logs secret-delete errors instead of swallowing them. - clientFor distinguishes a secret-store failure from "not configured" so the UI doesn't render the configure-CTA for transient backend errors. - Unify ErrNotConfigured wire code: HTTP and WebSocket both return "JIRA_NOT_CONFIGURED" via a shared constant. - JiraLinkButton strips a leading "PROJ-123: " before re-prefixing so re-linking a task replaces the prefix instead of stacking it. - jira-settings clears testResult on save/delete so the alert reflects only the currently-saved credentials. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(jira): cover Reveal error path in clientFor Reveal-error case is distinct from "no config row" — the former wraps the underlying error, the latter returns ErrNotConfigured. Without this test the changed contract has no regression coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): route secret adapter Set through Exists Treating every Get error as "not found" turned a transient DB error on an existing row into a constraint-violation Create that masked the real cause. Reuse Exists so the "secret not found:" prefix is the only path that falls through to Create. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(jira): cover jiraSecretAdapter.Set branching Routing Set through Exists changed the contract: a transient DB error in Get now propagates instead of cascading into a constraint-violation Create. Add three tests so a future refactor cannot regress this without CI catching it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): split Reveal errors and guard Save for new configs - resolveCredentials now mirrors the clientFor split: a Reveal error surfaces as `read jira secret: %w` with a warn log instead of collapsing into "no token stored — paste one to test". - disableSave gains the same missing-secret guard as disableTest so a brand-new config can no longer POST an empty secret and leave the workspace stuck with hasSecret=false. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
b04967bf70 | fix(web): show skeleton in file tree header while workspace path loads (#704) | ||
|
|
7a2160f1f9 | feat: show repository scripts in dockview "+" menu (#703) | ||
|
|
a79fd85074 | fix(sprites): use bash for prepare scripts and fix pnpm install in sprites env (#701) | ||
|
|
db5999a618 | feat(web): distinguish CI-passed PRs awaiting review from ready-to-merge (#702) | ||
|
|
d431688557 | feat(web): auto-open plan panel with unseen-changes indicator (#650) | ||
|
|
22ebba30b3 | feat(skills): add /spec for writing feature specs (#700) | ||
|
|
74d44bd26b | feat(acp): support claude-acp Monitor tool and fix incremental tool_call updates (#698) | ||
|
|
9dc1065010 | fix(web): align sidebar filter toolbar height with panel headers (#699) | ||
|
|
efd68db15b | fix: derive sidebar session state from most active session (#697) | ||
|
|
627b4aee98 | fix: auto-resume failed sessions with silent workspace-restore fallback (#696) | ||
|
|
7efca36a2c |
release: app v0.37
release / build-web (push) Has been cancelled
release / docker (push) Has been cancelled
release / build-bundles (aarch64-linux-gnu-gcc, arm64, linux, ubuntu-latest, linux-arm64) (push) Has been cancelled
release / build-bundles (amd64, darwin, macos-14, macos-x64) (push) Has been cancelled
release / build-bundles (amd64, linux, ubuntu-latest, linux-x64) (push) Has been cancelled
release / build-bundles (arm64, darwin, macos-14, macos-arm64) (push) Has been cancelled
release / build-bundles (x86_64-w64-mingw32-gcc, amd64, windows, ubuntu-latest, windows-x64) (push) Has been cancelled
release / publish-release (push) Has been cancelled
|
||
|
|
75f1732421 |
feat: add GitHub token injection for remote executors and Docker session resume (#654)
* feat: add GitHub token injection for remote executors and Docker session resume - Add global GitHub access token configuration (backend + frontend) - Inject GitHub token into Docker and Sprites executors via env vars and Git credential helper - Transition Docker executor from host mounting to repository cloning - Implement Docker session resume (reconnect to existing containers after backend restart) - Add shell validation using exec.LookPath in agentctl - Add scriptengine providers for agent install and agentctl placeholders * fix: format github-status.tsx with prettier * fix: resolve lint issues (goconst, errcheck, file-length) * fix: address code review findings - Fix resolveGHCLIToken to check both GITHUB_TOKEN and GH_TOKEN guards - Fix credential helper to use \\ fallback - Change /bin/bash to /bin/sh in credential helper for Alpine compat - Strip embedded token from .git/config after clone in prepare scripts - Extract buildLaunchAgentRequest helpers to stay under 80-line limit - Add tests for GitHubAuthProvider, injectTokenIntoURL, reconnect validation - Add GH_TOKEN-only test case for resolveGHCLIToken * refactor(executor): extract GitHub credential resolution to separate file Move injectGitHubToken, injectGitHubTokenFromCLI, resolveRemoteCredentials, resolveAuthSecrets, methodIDToEnvVar, resolveGHCLIToken, and detectGHToken out of executor_execute.go (was 803 logical lines, over the 800-line limit after merging main) into a new executor_credentials.go file. Also adds the nil-guard on secretStore in resolveAuthSecrets that was flagged by review. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c092fdeb1c | fix: ui polish — unified topbar, selector consistency, quick actions editor improvements (#693) | ||
|
|
1fc3544625 | fix: subtask sessions inherit agent profile from parent task (#692) | ||
|
|
046ac5c1b6 |
feat(web): add Ctrl+F search to session, plan, and terminal panels (#686)
* feat(backend): add session-scoped message search endpoint
Adds SearchMessages across repository/service/handler layers and a new
WebSocket action "message.search". Returns lightweight hits with a
60-char snippet around the first match. Session-scoped only: results
never leak across sessions. Uses dialect-aware LIKE/ILIKE so it works
on SQLite today and Postgres later.
* feat(web): add shared panel search bar and Ctrl+F hook
Introduces the reusable PanelSearchBar overlay and usePanelSearch hook
used by the per-panel search features. The hook listens on the window
and fires only when focus is within the panel root, preventDefaults the
browser find-in-page, and wires Esc to close. PanelRoot becomes a
forwardRef so panel components can expose their root element to the
hook. Adds SHORTCUTS.FIND_IN_PANEL plus shared highlight/flash styles.
* feat(web): add Ctrl+F search to terminal panels
Mounts the xterm addon-search on both passthrough and shell terminals,
exposing next/prev plus case-sensitivity and regex toggles. Invalid
regex surfaces an inline error without firing the search. Because xterm
consumes keystrokes before React, the Ctrl/Cmd+F intercept is added to
attachCustomKeyEventHandler so the shortcut opens the bar from inside
the terminal. Results counter comes from onDidChangeResults; decorations
clear on close.
* feat(web): add Ctrl+F search to plan panel
Adds a custom TipTap extension that builds a ProseMirror DecorationSet
over the document and exposes setPlanSearchQuery / planSearchNext /
planSearchPrev / clearPlanSearch commands. The current match gets an
extra class and scrolls into view. Match count is read from the plugin
state via transaction subscription, so edits to the document update the
counter automatically. Find-only — no replace.
* feat(web): add Ctrl+F search to session chat panel
Wires the new backend message.search action into a debounced client
hook and renders a hits dropdown under the search bar, with the query
highlighted in each snippet. Clicking a hit scrolls the message into
view and flashes it. When the target message is older than what is
currently loaded, the hook loops through loadOlder pages until the
message renders, making search work across lazy-loaded history without
needing to scroll-and-wait manually.
* fix(web): lazy-load xterm addon-search to avoid SSR crash
addon-search is published as a UMD bundle that references `self` at
module load. Statically importing it breaks the Next.js static
prerender of /. Deferring the import to an effect keeps the module out
of the SSR graph without changing runtime behaviour.
* fix(web): propagate plan editor via state so plan-search hook re-runs
The plan-search hook subscribed to editor transactions inside a
useEffect that depended on a ref object. Because ref.current changes do
not trigger effect re-runs, if the Editor instance was not yet ready
when the hook first mounted, the transaction handler never attached and
the match counter stayed at 0 / 0. Tracking the editor via state (in
addition to the ref) causes the deps to change when the Editor becomes
available, so the handler attaches and the counter updates.
* feat(web): allow overriding message list renderer via ?renderer= query
Exposes the "native" / "virtuoso" choice at runtime so E2E coverage can
exercise both paths without a redeploy or code change. Defaults to the
existing "native" strategy when no override is present.
* test(web): add Playwright coverage for panel-scoped Ctrl+F search
Adds an e2e/tests/search/ suite with five specs:
- panel-search-shared: cross-panel contract (Ctrl+F opens, autofocus,
Esc closes, close button, state reset on reopen) parametrised over
session, plan, and terminal.
- terminal-search: xterm addon-search integration — key intercept
without leaking "f" to the PTY, case + regex toggles (incl. invalid
regex error), Esc does not reach the shell, backspace adjusts count.
- plan-search: TipTap plugin — highlight count, exactly one current
match, counter, Enter / Shift+Enter wrapping, close clears
decorations, no case/regex toggles in plan bar.
- session-search: WebSocket-backed hits list, "No matches" state,
click-to-flash, cross-session isolation, close, and virtuoso renderer
coverage via the new ?renderer= override.
- panel-focus-routing: bar mounts inside the focused panel only.
Three tests capture GIFs for the PR description via prCapture when
CAPTURE_PR_ASSETS is set (one per panel). Helpers live in
e2e/helpers/panel-search.ts and e2e/helpers/seed-session-messages.ts;
SessionPage gains search-bar locators.
* fix(web): prevent plan-search update loop on spurious editor transactions
TipTap fires `transaction` on every cursor move/click. The match-info
handler allocated a fresh `{current, total}` object each time, so React
always saw new identity, re-rendered, and could cascade with downstream
effects (e.g. `rehydrateCommentMarks`) into a "Maximum update depth
exceeded" loop. Use a functional setState with equality bail-out so the
handler only schedules a render when the counter actually changes.
* fix(web): retry terminal search once xterm addon-search finishes loading
The SearchAddon is dynamic-imported and was stored in a ref. The
query→findNext effect called `addonRef.current?.findNext(...)` — a
silent no-op if the addon wasn't loaded yet — and its deps didn't
include addon readiness, so a query typed before the import resolved
was dropped forever. Track the addon via state so the search effect
re-runs when it becomes available (and re-binds cleanly if the terminal
is ever re-created). Also deduplicate match-info updates via functional
setState + equality check.
* fix(web): route Ctrl+F to the last-clicked panel when focus is ambient
The chat panel's message list has no focus-capturing child, so clicking
on a message left `document.activeElement === <body>` and the existing
`isFocusWithin` gate silently fell through to the browser's native find.
Fix with two complementary mechanisms:
- `usePanelSearch`: track the most-recently-clicked panel via a
window-level pointerdown capture listener. When focus is ambient
(body/html), that panel claims Ctrl+F. Window-level capture dodges
any descendant `stopPropagation` that would skip a container-level
listener.
- Chat panel root: tabIndex=-1 + onMouseDown that focuses the root when
the click target isn't already focusable. Normal path matches how
plan/terminal already work (TipTap/xterm capture focus on click).
* feat(web): show agent profile name and logo in session search hits
The hit list showed uppercase "AGENT"/"USER" with vivid blue/green
colors that clashed with the app's muted metadata aesthetic. Replace
with the actual author identity:
- Agent hits render the profile name (falling back to "Agent") with
the profile's <AgentLogo> inline — same mechanism session tabs use.
- User hits render "You" with a subtle `text-primary/80` tint that
echoes the user bubble's `bg-primary/30`, keeping a light color
differentiator without reintroducing loud blue/green.
Added `useSessionAgentIdentity` (plus a `useSessionAgentProfile` helper)
that reads from the store via two primitive selectors — the id, then
the profile — and composes the final object inside `useMemo`. A naive
single selector returning `{label, name}` triggers the "getSnapshot
should be cached" infinite loop in `useSyncExternalStore`.
* style: apply prettier formatting to search files
* fix(web): inline resolveStrategy in useMemo to satisfy react-hooks/use-memo
* fix(backend): escape LIKE metacharacters in message search
User query of `%` previously built LIKE pattern `%%` which matched every
message in the session; `file_name` also matched `filename`, `file name`,
etc. Escape the three LIKE metacharacters (`\`, `%`, `_`) before wrapping
the pattern, and declare `ESCAPE '\'` on the clause. Add regression tests
covering wildcard literals and the literal-percent happy path.
* fix(backend): slice search snippet in rune space to keep UTF-8 valid
`buildSnippet` indexed and sliced on byte offsets, so content containing
multi-byte characters (emoji, CJK, accented letters) could be cut
mid-rune — `encoding/json` then replaced the invalid bytes with `\ufffd`
in the websocket response. Switch to rune slicing and per-rune
case-folding via `unicode.ToLower`; this also avoids the
`strings.ToLower` byte-length-change pitfall (e.g. Turkish İ, German ß).
Add a handler test covering emoji, CJK, and large multi-byte content.
* fix(web): cancel backfill loop when user clicks a newer session hit
`setActiveHit` awaits up to 40 `loadOlder()` iterations when the target
message isn't in the DOM. If the user clicked a second hit while those
awaits were in flight, the in-progress loop kept running and eventually
scrolled to the original target, overriding the user's intent. Gate the
loop on a generation counter so only the most recent call's backfill
drives the scroll.
* fix(web): gate plan-search scroll side-effect on dispatch
TipTap invokes commands with `dispatch === undefined` during `can()`
checks to test applicability (e.g. before enabling a menu item). The
next/prev commands called `scrollToMatch` outside the `if (dispatch)`
guard, so a dry-run invocation would scroll the editor to a match the
user didn't navigate to. Move the scroll inside the dispatch block.
* fix(web): address review nits on search UX
- formatTime now checks Number.isNaN(d.getTime()) before formatting;
Invalid Date doesn't throw, so the catch branch was unreachable and
hits with a malformed created_at rendered the literal "Invalid Date".
- prefers-reduced-motion now silences the .search-flash animation
alongside the other custom keyframe animations.
- planScript escapes `title` symmetrically with `content` so callers
passing arbitrary strings don't produce malformed JSON.
* fix(web): fold description into opts to keep seedTask under param limit
`seedTask` had 6 parameters, violating the repo's 5-parameter cap and
failing CI lint. Fold `description` into the existing `opts` object and
update all callsites.
* test(web): add unit tests for useSessionSearch + remove redundant ternary
Cover the non-trivial hook logic called out in review: debounced fetch
coalescing, request-ID cancellation for superseded queries, null
sessionId short-circuit, and close() resets. Also simplify the
`matches.length ? 0 : 0` ternary flagged as a dead branch — both sides
were already 0.
* fix(web): exclude PanelRoot (tabindex=-1) from the focus short-circuit
`target.closest("[tabindex]")` was matching PanelRoot itself (which has
`tabIndex={-1}` so it can receive programmatic focus), causing the guard
to early-return and the `panelRef.focus()` call to never fire — defeating
the Ctrl+F focus-routing that this handler was added for.
Narrow the selector to `[tabindex]:not([tabindex='-1'])` so it only
short-circuits on genuinely interactive elements.
* fix(web): read isReadOnlyMode from live ref in shell-terminal key handler
`attachCustomKeyEventHandler` is called once and has no dispose hook, so
the inner handler keeps its initial closure forever. Other values
(sessionId, send, onFindInPanel) are read via `stateRef` so they stay
current, but `isReadOnlyMode` was captured by closure — if the prop ever
toggled mid-session, the Cmd+Arrow→Home/End path would continue firing
against a read-only terminal. Include it in the state ref for
consistency.
* fix(web): flatten doubly-nested seedTask opts in virtuoso spec; drop unused MODIFIER
The regex-based transform in
|
||
|
|
ed97ee82f6 | docs: improve commit skill with mandatory verify and pre-commit check (#691) | ||
|
|
72451e6f58 | fix: recover from stale execution ID when auto-starting agent on prepared workspace (#690) | ||
|
|
ccc62d9324 |
feat(github): configurable quick-action presets and PR branch checkout (#689)
* feat(github): configurable quick-action presets and PR branch checkout - Add workspace-scoped action presets (icon, label, hint, prompt template) for PRs and Issues, editable from GitHub settings with reset-to-defaults. - When starting a task from a PR, check out the PR head branch directly in the worktree instead of branching off main. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): extract action-preset service to keep service.go under line limit Splits the preset CRUD methods out of the oversized service.go and applies prettier formatting to three frontend files flagged by CI. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): address review feedback on action presets - Prevent infinite refetch loop when fetching presets fails by tracking attempted workspaces in a ref instead of re-entering the effect on every loading-state change. - Drop unused savePR/saveIssue helpers from the hook; only save() is consumed. - Reorder the init spread in updateGitHubActionPresets/resetGitHubActionPresets so callers can't accidentally overwrite method/body. - Tighten the GitHubActionPreset.icon type with string & {} so known icon keys keep autocomplete. - Sharpen the eye-icon test to assert the exact IconEye mapping. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): address second round of review feedback - Seed preset editor drafts from current store state via lazy initializers so a warm mount shows the user's saved customisations instead of silently reverting to defaults when Save is clicked. - Guard Reset button with its own in-flight state to prevent double-firing. - Stop freezing resolved defaults into the stored row on a partial update; persist only what the store has for the untouched kind so future default improvements still reach those workspaces. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(github): tighten quick-actions editor UI Replace the stacked PR/Issue editors and multi-line preset cards with a tabbed layout and single-line rows. Each row shows icon · label · hint inline; an "Edit prompt" toggle reveals the template textarea only when needed. Drops the redundant section headings, up/down reorder buttons, and verbose template helper label (hint moved into the textarea placeholder). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): widen icon select trigger so the icon is visible The icon select trigger had a fixed 40px width which the SelectTrigger's built-in chevron, padding, and icon gap all competed for, clipping the icon out of view. Drop the width override so the trigger sizes to its content. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
44dec297e1 | fix(web): apply initialValues when TaskCreateDialog mounts already-open (#688) | ||
|
|
85aa3ac0aa |
feat(github): add /github page for PRs and issues (#687)
* feat(github): add /github page for PRs and issues Dedicated page under /github lists pull requests and issues across the authenticated user's repos with preset filters, custom GitHub search queries, per-repo narrowing, saved queries, and one-click task launching for any PR or issue. Performance: - Cache user PR/issue search responses for 30s with singleflight to coalesce concurrent misses, so preset switching and pagination avoid upstream round trips. - Replace the per-row PR status fetch (N+1) with a batch endpoint POST /api/v1/github/prs/statuses that fans out concurrently on the backend with a semaphore and caches per-PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * style: apply Prettier formatting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address review feedback on PR cache + status batching - Harden searchCacheKey against separator collisions from user-controllable customQuery (length-prefix encoding). - Drop repositories.loaded hydration when inner workspace fetch fails so the client refetches instead of trusting a partial snapshot. - Guard useGitHubSearch with a monotonic request counter to drop stale responses when filter+page changes overlap. - Fix usePRStatuses teardown: depend only on composed key so a new array identity per render doesn't cancel the in-flight batch. - Correct godocs (duplicate, outdated state:open claim). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address additional review feedback - Restore state:open for issue watcher: buildIssueSearchQuery no longer injects it as a default, so existing watches were starting to surface closed issues. Inject state:open in buildIssueFilter so only the watcher path is affected; the /github page presets already set their own state qualifiers. - countCheckResults now excludes in-progress checks from the denominator so a `1/3` badge reflects pass rate on completed checks, not a mix of running and failed. The pending state is still carried via ChecksState. - usePRStatuses retries after a transient fetch error (only mark the key as completed once the fetch resolves). This also fixes missing badges under React Strict Mode's intentional double-mount, since the first effect's cleanup fires before the response lands and the guard stays unset for the second mount to retry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(github): add tests for my-github hooks Covers use-committed-query, buildParams from use-github-search, use-saved-presets readStorage validation, use-known-repos recordForKey accumulator semantics, and use-pr-statuses batch fetch lifecycle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address review feedback on search cache and repo accumulator - Clamp perPage before composing the search cache key so identical pages (e.g. perPage=150 and =100) share a cache entry instead of creating duplicates keyed on the raw value. - Add resetKnownReposStore and call it on /github unmount so the module-level repo accumulator doesn't carry across navigations. - Rename use-pr-statuses test to match its assertion (it verifies stale statuses are cleared when the key changes and fetch fails). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * style: apply Prettier formatting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): distinguish blocked from dirty in PR mergeable badge GitHub's blocked state (required reviews/checks/branch protection) is not a merge conflict, so don't label it "Conflicts". Surface it as "Blocked" with the pending tone; keep "Conflicts" for dirty. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address remaining /github code review comments - Route PR status + batch through handleSearchError so ErrNoClient surfaces as 503 instead of 500 (matches the search endpoints). - Sort workflow steps by position in the quick-task-launcher default pick, matching the convention in issue-watch-dialog. - Make PresetItem keyboard-accessible: role=button, tabIndex, and Enter/Space handling on the wrapping div (it contains a nested delete button, so it can't itself be a button). - Extract useRepoOptions out of useGitHubPageState to bring the hook back under the 100-line complexity limit. - Honor ctx.Done() when acquiring the PR-status batch semaphore so a cancelled HTTP request releases queued goroutines instead of running each upstream fetch to completion. - Clarify parsePaginationQuery doc: the upper bound (100) is enforced by clampSearchPage in the client layer, not here. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(web): address /github review follow-ups - Merge duplicate @/lib/utils imports in pr-list. - Use rel="noopener noreferrer" on external PR/issue links (codebase convention) and add cursor-pointer per the UI guidelines. - Rewrite onOpenSaveDialog as an explicit if-block so the callback returns void rather than boolean | void. - Use PaginationEllipsis in its own PaginationItem so the ellipsis and the adjacent page link are separate list items instead of visually fused into a single cell. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(web): derive QuickTaskLauncher dialog state from props Replaces the useEffect+setState anti-pattern with useMemo-derived state driven directly by the payload prop. Drops the eslint suppression that was needed for react-hooks/set-state-in-effect. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
7e938f6bdb |
fix(worktree): bound git ref-inspection with context timeout (#685)
* fix(worktree): bound git ref-inspection with context timeout branchExists and currentBranch spawned raw exec.Command with no context and no timeout, and both were called while Manager.Create held the per-repo mutex. A hung git subprocess (credential helper, stuck filter, filesystem stall) kept the lock in Manager.repoLocks forever, blocking every subsequent task on the same repository until the backend was restarted. Route both helpers through newNonInteractiveGitCmd with a new defaultGitInspectTimeout (10s) so ctx cancellation and bounded deadlines propagate to the subprocess. Also log lock-hold duration on release for future diagnosis. * fix(worktree): address review feedback on inspect-timeout change - branchExists/currentBranch now log the ctx error when the bound fires, so a hung git surfaces in logs instead of only as a misleading "branch not found" (CodeRabbit). - inspectTimeout is a Manager field (defaults to defaultGitInspectTimeout) so tests can shrink it; added an isolation test that verifies the internal bound fires even when the caller ctx has no deadline (Claude). - Route setUpstreamIfExists through newNonInteractiveGitCmd so it also gets non-interactive env + WaitDelay while repoLock is held (Claude). - Simplify TestCreate_HangingRevParseReleasesRepoLock: drop redundant sync.WaitGroup and extract hangOnRevParseScript constant shared by all four tests (CodeRabbit). |
||
|
|
e1d21e39a9 | feat(web): collapse subtasks in sidebar (#662) | ||
|
|
b77b2802ce | feat(workflows): review uncommitted changes and add git safety rails (#684) | ||
|
|
367700f507 | ci: stop Claude code review workflow from committing to PRs (#683) | ||
|
|
de040a8987 | fix: gateway auth injection and cumulative diff error handling (#682) | ||
|
|
7076a40805 | chore: change default ports to avoid common conflicts (#681) |