Commit Graph

807 Commits

Author SHA1 Message Date
Carlos Florêncio 7076a40805 chore: change default ports to avoid common conflicts (#681) 2026-04-23 18:09:53 +01:00
José Almeida c078438621 refactor: unify task.updated via single publisher and shared mapper (#676) 2026-04-23 15:16:31 +01:00
Carlos Florêncio d57a1d2680 fix: make task move event handlers asynchronous to prevent HTTP timeouts (#680) 2026-04-23 13:53:31 +01:00
Carlos Florêncio 6ffb157d7e fix: user workflow deadlock (#677) 2026-04-23 09:54:06 +01:00
Carlos Florêncio 86b3099631 fix(backend): unblock Resume for FAILED/CANCELLED task sessions (#670) 2026-04-21 21:04:35 +01:00
Carlos Florêncio e61e09cc1e fix(backend): prevent duplicate --allow-indexing in Auggie passthrough preview (#675) 2026-04-21 19:57:22 +01:00
Carlos Florêncio 222bba8d08 fix(backend): send auto-start prompt after on_turn_complete context reset (#669) 2026-04-21 19:56:53 +01:00
João Salavisa e8781a6443 refactor: move system prompts from Go constants to external config files (#673)
* refactor: move system prompts from Go constants to external config files

* fix: address review — add session handover tests and Resolve doc comment

* fix: single-pass placeholder replacement and remove no-op InterpolatePlaceholders

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-04-21 19:29:46 +01:00
João Salavisa c11a6bbfd2 feat(github): add issue watcher with task creation and auto-cleanup (#672)
* feat(github): add issue watcher with task creation and auto-cleanup

Add a GitHub Issue Watcher feature that polls for new issues matching
configurable filters and automatically creates tasks for them. This
complements the existing PR Review Watcher with the same patterns.

Backend:
- Issue watch CRUD (models, store, service, controller, WS handlers)
- Poller integration with configurable intervals
- Event-driven task creation via orchestrator
- Repository resolution and worktree support for issue tasks
- Auto-cleanup of tasks for closed issues (CleanupClosedIssueTasks)
- GetIssueState client method across all implementations
- Default branch auto-detection from cloned repo HEAD

Frontend:
- Issue watch settings UI (table, dialog, placeholders)
- Issue link icon on task cards (green circle-dot)
- API client, store slice, and hook for issue watches
- SSR and WS hydration of issue metadata

* fix: resolve golangci-lint errors (goconst, nestif, dupl) and prettier formatting

* fix(github): fix issue watch update shape, trigger key, and label quoting

- httpUpdateIssueWatch now returns the full IssueWatch object instead of
  {updated:true} so the frontend store correctly refreshes after edits
- httpTriggerIssueWatch returns new_issues_found to match TriggerIssueResponse
  type; was returning new_issues so the toast count was always 0
- buildIssueFilter uses conditional quoting instead of fmt.Sprintf %q to
  avoid Go escape sequences in GitHub search qualifiers

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

* fix: resolve eslint warnings (max-lines-per-function, no-nested-ternary)

* test(github): add issue watcher tests and fix review feedback

- Add TestInterpolateIssuePrompt (4 cases: empty/all/none/partial)
- Add TestCreateIssueTask (3 cases: skip/reserve+assign/release on fail)
- Add TestShouldDeleteIssueTask (4 cases: open/closed+sessions/closed/error)
- Fix WS response key new_issues -> new_issues_found
- Fix fmt.Printf -> fmt.Fprintf(os.Stderr) in store.go
- Add CleanupClosedIssueTasks call in WS trigger handler

* fix: address PR review feedback and CI failures

- Split test files to fix revive file-length-limit (service_test.go,
  event_handlers_github_test.go)
- Add session checker guard in CleanupClosedIssueTasks
- Normalize poll intervals on UpdateIssueWatch
- Add cleanup call in TriggerAllIssueChecks loop
- Log cleanup errors in controller and WS handler
- Prefer refs/remotes/origin/HEAD in detectGitDefaultBranch
- Add aria-label to issue task icon
- Fix TypeScript type errors: add linkedIssues to GitHubSlice,
  fix hook exports, remove invalid imports

* fix: split test files exceeding revive limit and clean up minor issues

- Split service_test.go (883→779 lines): move TestTriggerPRSync_* to
  service_pr_sync_test.go
- Split event_handlers_github_test.go (806→677 lines): move
  TestCreateReviewTask_* and helpers to event_handlers_github_review_test.go
- Remove redundant s.taskSessionChecker nil-check inside shouldDeleteIssueTask
  (early guard at CleanupClosedIssueTasks already ensures it is non-nil)
- Remove unreachable dead guard in detectAndPersistDefaultBranch
  (repo.DefaultBranch is always "" at that call site)
- Fix bogus #0 issue badge in task-session-sidebar.tsx when issueNumber is absent

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

* fix: address Claude review — poll clamp and prompt injection

- Handle v <= 0 in UpdateIssueWatch poll interval (fall back to default)
- Remove {{issue.body}} from default template to prevent prompt injection
- Update test expected output for new default template

* fix: remove {{issue.body}} from frontend DEFAULT_ISSUE_WATCH_PROMPT

Mirrors the backend fix — the UI pre-fills the prompt so the backend
fallback was never reached. The placeholder stays in
ISSUE_WATCH_PLACEHOLDERS for explicit opt-in.

* feat(github): add isIssueWatch sidebar filter dimension

Add isIssueWatch boolean to KanbanState, TaskSwitcherItem, and sidebar
filter registry — mirroring the isPRReview pattern. Issue-watch tasks
(created from issue_watch_id in metadata) can now be explicitly filtered
in the sidebar, just like PR review tasks.

Files changed:
- sidebar-view-types: add isIssueWatch dimension
- filter-dimension-registry: add Issue watch filter meta
- apply-view: add isIssueWatch extractor
- kanban types: add isIssueWatch field
- TaskSwitcherItem: add isIssueWatch field
- use-all-workflow-snapshots: derive isIssueWatch from metadata
- ssr/mapper: derive isIssueWatch from metadata
- task-session-sidebar: pass isIssueWatch, extract helpers to fix complexity

* fix: correct deleteIssueWatch response type to match backend

Backend httpDeleteIssueWatch returns {"deleted": true}; the frontend
type was { success: boolean } — a mismatch with no functional impact
(return value is ignored) but wrong per TypeScript contract.

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

* refactor: extract metadata helpers to shared lib/metadata-utils.ts

- Move isPRReviewFromMetadata, isIssueWatchFromMetadata, and
  issueFieldsFromMetadata to a shared module to eliminate duplicate
  definitions (sonarjs/no-identical-functions).
- Fix deleteIssueWatch response type: { success: boolean } -> { deleted: boolean }
  to match the actual backend response.

* style: fix prettier formatting

* test(web): add tests for isIssueWatch filter dimension and metadata-utils

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: fix prettier formatting in test files

* fix(github): cascade-delete issue watch tasks on watch deletion and add isPRReview to SSR mapper

- DeleteIssueWatch now wraps in a transaction and deletes related
  github_issue_watch_tasks rows before removing the watch; prevents
  orphaned dedup rows accumulating after a watch is deleted
- snapshotToState in mapper.ts now also sets isPRReview from metadata,
  matching the mapping already done in use-all-workflow-snapshots.ts

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

* style: extract duplicate string constant in metadata-utils test

* style: extract repeated WS error strings into constants in handlers.go

goconst min-occurrences: 3 — 'invalid payload' (6x) and 'id required' (4x)
both exceed the threshold. Extract to package-level constants.

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

* style: extract repeated issue key strings into constants in event_handlers_github.go

goconst min-occurrences: 3 — 'issue_number' (9x) and 'issue_watch_id' (5x)
both newly introduced in this PR exceed the threshold.

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

* style: fix prettier formatting in metadata-utils test

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:32:46 +01:00
João Salavisa dccff23cc0 feat(backend): per-launch authentication for agentctl (#666)
* feat(backend): per-launch authentication for agentctl

Add nonce-based handshake protocol to secure all agentctl HTTP/WS
endpoints. On launch, the parent (backend) passes a one-time bootstrap
nonce via AGENTCTL_BOOTSTRAP_NONCE env var. agentctl generates its own
auth token, enables Bearer auth immediately, and returns the token to
the parent via POST /auth/handshake (validated by nonce).

Standalone, Docker, and Sprites executors all use the same handshake
flow. Removes the old AGENTCTL_AUTH_TOKEN env var approach. For
Docker/Sprites, the retrieved token is persisted encrypted in
SecretStore (AES-256-GCM) for crash recovery.

Security properties:
- Auth enabled from boot (no unprotected window)
- Token never in env vars (only the burned nonce)
- Nonce is one-shot (replay impossible)
- Constant-time token comparison (timing-safe)

* fix: check fmt.Sscanf return value in integration test

* fix: address review feedback - constant-time nonce comparison, safe ID truncation

* fix(backend): exempt MCP routes from agentctl auth middleware

The agent subprocess connects to agentctl's MCP endpoints (/sse, /message,
/mcp) to use kandev tools (create_task, ask_user_question, etc.). These
endpoints run on the same HTTP server as the authenticated API, but the
agent process doesn't possess the auth token — it was only exchanged
between the backend and agentctl during the handshake.

Exempt /sse, /message, and /mcp from the Bearer auth middleware since the
agent process runs in the same trust boundary as agentctl.

* refactor(backend): remove query param token fallback and document SetAuthToken constraint

Remove the ?token= query parameter fallback from extractBearerToken —
tokens in URLs leak via access logs, referer headers, and browser history.
All WS callers already use Authorization headers via wsAuthHeaders().

Document SetAuthToken's concurrency constraint: it must not be called
concurrently with HTTP requests on the same client.

* fix(backend): enforce mutex in ControlClient/Launcher auth, fix Start() lock scope

- Add sync.Mutex to ControlClient; protect SetAuthToken and AuthToken() reads
- Guard Launcher.AuthToken() with l.mu (written under lock in Start, read unprotected)
- Extract buildAndStartProcess and performHandshake from Start() (was 106 lines, over 80-line limit)
- Release l.mu before blocking health/handshake calls so monitorExit() can close
  l.exited — previously the fast-fail path in waitForHealthy was unreachable
- Update stale executor_backend.go comment (standalone no longer uses env var auth)

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
2026-04-21 14:00:49 +01:00
João Salavisa 88f69df702 fix: include archived tasks in completed tasks over time chart (#668)
* fix: include archived tasks in completed tasks over time chart

Archived tasks without a session completed_at were excluded from the
Completed Tasks Over Time chart. Use COALESCE(session.completed_at,
task.archived_at) as the completion date so archived tasks fall back
to their archive timestamp.

* fix: address review feedback - isolate completion paths in test

- Remove archived_at from task-regular so it only counts via session
  completed_at (CodeRabbit nitpick)
- Add task-last-step-no-dates to verify COALESCE IS NOT NULL filter
  excludes tasks with neither date (Claude suggestion)

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-04-21 01:36:04 +01:00
João Salavisa 5bb10190c2 fix: remove duplicate WebSocket event subscriptions (#667)
* fix: remove duplicate WebSocket event subscriptions for messages and session state

* fix: remove unused broadcastRecorder.all method

* fix: address review comments — replace tautological test with subscription count guard

* fix: update test comments per review feedback

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-04-21 01:04:43 +01:00
Carlos Florêncio 2d1a868107 feat: configurable CLI flags per agent profile (#653) 2026-04-20 23:42:14 +01:00
Carlos Florêncio 1649967713 release: app v0.36
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
v0.36
2026-04-20 23:33:05 +01:00
Carlos Florêncio cb7546e323 fix(web): prevent kanban topbar search from overlapping right buttons (#661) 2026-04-20 23:22:34 +01:00
Carlos Florêncio 5f1c4a13e4 fix(web): enable Start task button when workflow provides agent override (#665) 2026-04-20 23:10:50 +01:00
Carlos Florêncio 31e45dcfec fix(cli): default dev mode db to <repo>/.kandev-dev/data (#664) 2026-04-20 22:49:26 +01:00
Carlos Florêncio d81baf3fca fix(web): stop "Preparing workspace" flashing on step move and refresh stale chats (#663) 2026-04-20 22:48:07 +01:00
Carlos Florêncio c31fa98e61 fix(cli): detect standalone server.js at non-default path (#660) 2026-04-20 22:34:15 +01:00
Carlos Florêncio 2345f69e63 fix(backend): persist attachments on queued message when dequeued (#659) 2026-04-20 22:32:24 +01:00
Carlos Florêncio 7e7865a82c fix(backend): silence spurious errors during Ctrl+C shutdown (#658) 2026-04-20 22:31:30 +01:00
Carlos Florêncio 6909fe6854 feat(web): session tabs on kanban preview panel (#648) 2026-04-20 22:31:09 +01:00
João Salavisa eb8583e0e1 fix: exclude ephemeral tasks from stats page queries (#656)
* fix: exclude ephemeral tasks from stats page queries

* test: add ephemeral exclusion tests for all stats query functions

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-04-20 22:15:19 +01:00
João Salavisa 012a558174 fix(web): add edit icon hint to utility agent rows (#657)
* fix(web): add edit icon hint to utility agent rows

* fix(web): remove orphaned group class from utility agent rows

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
2026-04-20 22:01:34 +01:00
Carlos Florêncio 4a9d1a1d7e fix(workflows): tighten default template prompts for commits, todos, and PR review (#655) 2026-04-20 21:55:33 +01:00
Carlos Florêncio c0797e8bce release: app v0.35
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
v0.35
2026-04-20 21:06:48 +01:00
Carlos Florêncio 986410ab2e fix: release script tags fetching 2026-04-20 21:06:28 +01:00
Carlos Florêncio f4b5abef1a fix(web): show repo name instead of full path in task sidebar (#652) 2026-04-20 20:04:32 +01:00
Carlos Florêncio ad00b90d2a fix(backend): unstick agent session when cancel times out (#651) 2026-04-20 20:02:19 +01:00
José Almeida d209d2ce2a feat(web): sidebar filter UX polish — align ops, group steps by workflow (#647)
* fix(web): align operator column across sidebar filter rows

Dimension select was `flex-1`, which made the op column drift horizontally
between rows — especially on boolean dimensions where the value input is
omitted. Fix by pinning the dimension and op selects to fixed widths so
only the value column flexes, keeping the middle "is/in/not in" aligned
across every row.

* feat(web): group workflow steps by workflow in sidebar filter dropdowns

The Workflow step filter previously flattened steps from every workflow
into one unlabelled list, making it impossible to tell which workflow a
step belonged to. Tag each step option with its workflow name and render
the single- and multi-select dropdowns with a labelled group + separator
per workflow. Other enum dimensions stay flat (backward-compatible via
an optional `group` field on the option).

* refactor(web): extract shared helper for sidebar filter option groups

Both the single- and multi-select value dropdowns built group buckets
with the same consecutive-key loop, which implicitly assumed options of
the same group arrived back-to-back. Extract a `buildOptionGroups`
helper backed by a Map so the adjacency assumption goes away and the
rendering call sites shrink to one line each. Also include the stable
`option.value` in the cmdk `CommandItem` value so same-titled steps
inside one workflow don't collide on identity/search.

* fix(web): truncate sidebar filter single-select value within popover

The single-select value trigger and the free-text input were `flex-1`
without `min-w-0`, so a long value (e.g. repository slug) pushed the
row wider than the popover and bled the remove button past the right
edge. The multi-select trigger already had `min-w-0`, which is why the
"in" operator rendered correctly. Add `min-w-0` to both so the flex
items can shrink and the built-in SelectValue `line-clamp-1` ellipsis
engages.
2026-04-20 19:32:36 +01:00
Carlos Florêncio 0b5919c2a2 feat(web): explain why Start task button is disabled via hover tooltip (#649) 2026-04-20 19:30:01 +01:00
Carlos Florêncio e1f0632325 fix(web): anchor PR detail panel to session group on auto-open (#646) 2026-04-20 18:52:45 +01:00
Carlos Florêncio d38987d326 fix(backend): push git snapshot on session focus (#645) 2026-04-20 18:44:06 +01:00
José Almeida f6f45b5777 feat: add filter/group/sort and saved views to task sidebar (#644)
* feat(web): add sidebar view types and apply-view engine

Introduces the typed representation of a sidebar view (filter clauses,
sort spec, group key) and the pure engine that applies a view to a list
of tasks to produce a filtered, sorted, grouped result.

* feat(web): add built-in sidebar view presets

Provides the four shipped presets (All tasks, No PR reviews, Active,
Archived). The 'No PR reviews' preset filters out PR-watcher tasks that
would otherwise crowd the sidebar.

* feat(web): persist sidebar views in ui store

Adds sidebar view state to the ui slice with draft semantics for saved
views, localStorage persistence of active view + user-created views, and
root store wiring.

* feat(web): add filter dimension registry and clause editor

Registry of filterable task dimensions (status, workspace, workflow, task
type, etc.) plus the primitive UI pieces — clause editor, sort picker,
group picker, and a hook that supplies operator and value options for a
selected dimension.

* feat(web): add sidebar filter bar, popover, and view manager

Composes the filter primitives into the user-facing surfaces: the top
filter bar, the popover that hosts clause editing and sort/group
selection, the chips that switch between views, and the manager for
saving, renaming, and deleting user views.

* refactor(web): extract NewTaskButton component

Pulls the Create Task button and its command-palette registration into a
standalone memoized component in preparation for reuse across sidebar
surfaces.

* feat(web): integrate filter/view system into task sidebar

Wires the filter bar, view chips, and apply-view engine into the task
sidebar (desktop and mobile sheet). Updates task-switcher to consume the
grouped list from the engine and adds mock data for tests and previews.

* test(web): add e2e coverage for sidebar filter/view system

Page object for the filter popover plus Playwright specs covering built-in
presets, clause editing, sort/group, saved-view draft semantics, and
persistence across reloads.

* refactor(web): trim sidebar built-in views to a single seeded default

Drop the "No PR reviews", "Active", and "Archived" built-in view presets
and the isBuiltIn protection flag. The sidebar now seeds a single "All
tasks" view on first load; users can rename, overwrite, or delete it like
any other view. Deletion is guarded against removing the last remaining
view so the user can never lock themselves out.

* fix(web): humanize executor type labels in sidebar filter and grouping

The executor-type filter dropdown and group-by label were rendering raw
backend identifiers like "worktree" and "local_docker". Route both
through the existing getExecutorLabel() helper so the sidebar shows
"Worktree" and "Local Docker" like the rest of the app.

* feat(web): replace "Has PR" filter with "Is PR review"

The old dimension matched any task with an associated PR, including
tasks a user manually linked. The new dimension isolates tasks created
by the GitHub PR-review watcher specifically, which is what the filter
is useful for — hiding a wall of auto-generated review tasks.

The flag is derived from the review_watch_id metadata key the backend
already stamps onto watcher-created tasks, threaded through the kanban
snapshot and into the sidebar TaskSwitcherItem. A small migration drops
unknown dimensions (including the old "hasPR") from stored views so the
popover does not crash when reading legacy data.

* fix(web): tighten sidebar filter popover layout

Reduce the popover width (w-96 → w-[22rem]), trim section padding
(p-3 → p-2.5) and label margins (mb-2 → mb-1.5), and drop the
"No filters applied." placeholder. Extract a shared SectionLabel to
keep the three section headers visually aligned.

* feat(web): show workflow step color dot in sidebar filter dropdown

Surface the step color on the workflow-step filter's value dropdown so
each option renders with the same coloured circle used elsewhere (task
switcher, kanban column, command panel). Adds an optional color field
on filter value options; only workflow-step options populate it today.

* feat(web): replace cramped in/not-in filter editor with a multiselect dropdown

The previous "in/not in" value input rendered selected values as tiny
10px pills inside a fixed-height row that overflowed and truncated as
the list of options grew. Replace it with a combobox-style multiselect
(Popover + Command) that shows a compact summary ("N selected" or the
single label with its color dot) and opens a searchable, scrollable
list with check indicators on click.

* fix(web): show workflow and step names (not UUIDs) in sidebar grouping

Group-by Workflow and Workflow Step rendered raw UUID strings as group
headers because `TaskSwitcherItem` did not carry the resolved names.
Pass `workflowName` and `workflowStepTitle` through `toSidebarItem` and
prefer them in the group extractors.

* fix(web): drop redundant true/false selector on boolean sidebar filters

Boolean dimensions (PR review, Archived, Has diff) previously exposed
both an operator (is / is not) and a value selector (true / false),
which gives four user inputs for two real states. Hide the value input
for boolean clauses and always store `true`; negation is expressed via
the operator. Rename "Is PR review" to "PR review" since the dimension
label no longer needs to read as a question.

* refactor(web): drop Sort by Repository and relabel State to Status

Sorting by repository duplicates what Group by Repository already
offers, and "State" leaks a backend term into the picker. Remove the
repository sort key (comparator, type, picker option) and rename the
state option to "Status". Stored views referencing the removed key are
migrated back to `state` at load time so the popover does not crash.

* fix(web): tighten sidebar filter popover section spacing

The FILTERS, SORT, and GROUP BY section labels each sat above their
controls with both padded containers (p-2.5) and a 6px bottom margin,
leaving visible dead space above each header. Reduce container padding
to p-2 and label margin to mb-1 so sections read as a compact stack.

* chore(web): apply prettier formatting drift in sidebar-filter area

* revert(web): undo store.ts prettier reformat to stay under max-lines cap

The packed single-line UIA declaration in store.ts is load-bearing for
the 600-line ESLint cap: the previous chore commit unpacked it to 9
lines and pushed the file to 609 lines, turning the max-lines warning
into a lint failure. Restore the packed layout (the /* prettier-ignore */
comment is there for exactly this reason).

* refactor(web): label boolean filter ops as Show/Hide instead of is/is not

* fix(web): pull section labels flush to sidebar filter popover borders

* refactor(web): inline rename and save-as into sidebar view header row

* refactor(web): split ViewHeaderRow into mode, display, and action subcomponents

* feat(backend): persist sidebar views in user settings

Adds a SidebarViews field to UserSettings (stored in the existing
users.settings JSON blob) plus validation in the service layer:
views are capped at 50, ids and names must be non-empty and unique.

* feat(web): hydrate and sync sidebar views to backend

Moves the source of truth for saved sidebar views from localStorage
to the user settings endpoint. Views hydrate from SSR on first load;
create/rename/overwrite/delete/duplicate/toggle-collapsed all PATCH
the backend with optimistic updates and revert-on-failure. The
active view id and draft remain per-device in localStorage. A
sync bridge surfaces PATCH errors as toasts and runs a one-time
migration to upload any pre-existing local views.

As a side effect, localStorage writes and PATCH side effects are
moved out of immer producers to avoid proxy-serialization risk.

* chore(web): apply prettier to store.ts sidebar actions block

* fix(web): run migrateView on SSR-hydrated sidebar views

Defensively drop clauses with unknown dimensions and reset stale sort
keys when views come from the backend, matching the localStorage load
path. Also notes the default-state module and sidebar views in the
store structure docs.

* docs: note default-state module and sidebar views in store structure

* fix(web): type sidebar view storage wrappers properly

Drop the as-unknown-as-SidebarView and as-never casts at call sites by
making the local-storage getters/setters accept the domain type and do
the JsonValue cast once inside the wrapper.

* refactor(web): stop PATCHing on sidebar group collapse toggles

collapsedGroups is per-device visual state; routing it through
mutateViews made every expand/collapse click fire a user-settings
PATCH. Keep it local (in-memory + localStorage cache); the next real
view mutation picks up whatever state is current.

* fix(web): skip sidebar view migration when settings hydrated from server

After SSR hydration the in-memory views are already the backend's
truth, so migrating re-POSTs the same data every page load. Gate the
one-shot migration on userSettings.loaded — only run if the server
didn't answer and the slice fell back to localStorage.
2026-04-20 13:44:24 +01:00
Carlos Florêncio 31140c8b9f fix(web): follow workflow step session switches in chat UI (#625) 2026-04-20 09:26:53 +01:00
José Almeida 375472cc02 fix(github): stop PR polling for archived tasks (#643)
* fix(github): stop PR polling for archived tasks

The PR watcher kept making GitHub API calls for tasks the user had
archived, wasting rate limit and surfacing stale checks. Hide archived
and orphaned watches from the poller via an INNER JOIN on tasks, and
proactively prune watches when task.updated (archived_at set) or
task.deleted events fire. Closes #989.

* fix(github): address review comments on PR watch pruning

- Detect archived_at from a non-empty string value instead of key
  presence, so a future null/zero archived_at in the task event payload
  doesn't silently prune watches on non-archive updates.
- Track task-event subscriptions on the Service and release them in the
  provider cleanup so handlers don't accumulate if the service is torn
  down and re-created while the event bus persists.
- Propagate RowsAffected errors from DeletePRWatchesByTaskID instead of
  silently discarding them.
2026-04-19 21:49:05 +01:00
Carlos Florêncio 4b55a69145 feat(web): vscode-style preview tabs for files, diffs, and commits (#622) 2026-04-19 17:39:07 +01:00
Carlos Florêncio b8d231a5cf fix: clear kanban snapshots when active workspace changes (#633) 2026-04-19 11:35:47 +01:00
Carlos Florêncio 66f0cc2a16 fix(web): persist agent profile mode through bulk-edit save (#626) 2026-04-19 11:34:43 +01:00
Carlos Florêncio 1f7e4dbb5e fix: stream setup script output and keep prepare panel on failure (#607) 2026-04-19 11:08:18 +01:00
João Salavisa 519de03da5 fix(agentctl): inject HTTP MCP server for Codex ACP support (#641)
* fix(agentctl): inject HTTP MCP server for Codex ACP support

Codex ACP only supports HTTP-based MCP servers (not SSE). The kandev
MCP server was only injected with SSE transport, causing it to be
filtered out by Codex ACP's capability check.

Now inject both SSE and HTTP variants of the kandev MCP server:
- SSE at /sse (for Claude Code)
- HTTP at /mcp (for Codex ACP)

The agent's capability filtering automatically selects the appropriate
transport based on what the agent supports.

* refactor(agentctl): address code review feedback

- Add name deduplication to filterMcpServersByCapabilities to prevent
  duplicate MCP server entries if an agent supports both transports
- Extract injectKandevMcpServers helper to centralize dual-transport
  injection logic in agent.go
- Add constants for MCP server name and transport types
- Add test for duplicate name deduplication

* fix(agentctl): add MCP injection to handleWSResetSession

Apply injectKandevMcpServers to session reset handler, ensuring MCP
tools remain available after context window reset.
2026-04-18 18:37:09 +01:00
Carlos Florêncio 4e5f16a0b0 ci: split Claude code review into separate same-repo and fork jobs (#642) 2026-04-18 17:46:20 +01:00
João Salavisa cda044316e fix(docker): route user shell to container instead of host (#638) 2026-04-18 17:01:53 +01:00
José Almeida 80eacf9e1c fix(web): use UUID fallback for attachments on non-secure contexts (#640) 2026-04-18 15:29:57 +01:00
Carlos Florêncio 8d407eab02 test(web): add e2e tests for tasks list view filters (#612) (#635) 2026-04-18 12:00:17 +01:00
Carlos Florêncio b8034cd06c fix(web): collapse repeated "Resumed agent" boot messages into the last one (#631) 2026-04-18 11:41:03 +01:00
Carlos Florêncio 5b04c9c675 perf: focus-gated git polling to reduce CPU on retained worktrees (#610) 2026-04-18 11:33:04 +01:00
João Salavisa 06cb48b300 fix(docker): use API version negotiation instead of hardcoded 1.41 (#636)
The default Docker API version was hardcoded to 1.41, which fails on
newer Docker daemons that require at least API version 1.44.

Change the default to empty string so the Docker client uses the
existing WithAPIVersionNegotiation() option to auto-negotiate a
compatible version with the daemon.
2026-04-18 11:06:32 +01:00
Carlos Florêncio 820e0b963a ci: use Claude App token for same-repo PRs, fallback for forks (#634) 2026-04-18 11:05:47 +01:00
José Almeida f63a9d5dc5 fix(web): wrap long paths in Discard Changes dialog (#620)
Long file paths could overflow the AlertDialog horizontally, pushing
the action buttons outside the visible area. Add break-words on the
description and overflow-wrap:anywhere on the filename span so paths
wrap mid-string instead of forcing the dialog wider than its max-w.
2026-04-18 10:32:24 +01:00