Commit Graph

126 Commits

Author SHA1 Message Date
Carlos Florêncio efd68db15b fix: derive sidebar session state from most active session (#697) 2026-04-25 13:44:06 +01:00
Carlos Florêncio 1fc3544625 fix: subtask sessions inherit agent profile from parent task (#692) 2026-04-24 21:31:36 +01:00
José Almeida 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 7152b936 over-nested one call in
session-search.spec.ts — `opts.description` received an object rather
than a string, so the virtuoso renderer test never received a proper
task description. Flatten it.

Also remove MODIFIER imports/declarations in terminal-search.spec.ts and
panel-search-shared.spec.ts that were left dangling behind `void` suppressors.

* fix(web): abort session-search backfill loop on close/unmount

Move reset logic into `close()` so the lifecycle is explicit (and avoids
the set-state-in-effect lint path the previous reset effect triggered).
Bump the `activeHitGenRef` generation counter on both `close()` and
unmount so an in-flight `setActiveHit` backfill can no longer issue
`loadOlder()` calls for a hidden UI.

Also extract `useDebouncedSearch`, `useSetActiveHit`, and a
`focusMessageElement` helper to keep `useSessionSearch` under the
100-line function cap.

* perf(web): skip PanelSearchBar debounce for session search

Session search already debounces the WS call inside `useDebouncedSearch`
(180ms), but the shared bar adds its own 150ms debounce on top — giving
a 330ms keystroke→request latency instead of the intended ~180ms.
Pass `debounceMs={0}` so the bar forwards queries immediately and the
hook-level debounce is the single source of timing truth.
2026-04-24 17:14:03 +01:00
Carlos Florêncio e1d21e39a9 feat(web): collapse subtasks in sidebar (#662) 2026-04-23 18:40:58 +01:00
Carlos Florêncio de040a8987 fix: gateway auth injection and cumulative diff error handling (#682) 2026-04-23 18:31:00 +01:00
Carlos Florêncio 7076a40805 chore: change default ports to avoid common conflicts (#681) 2026-04-23 18:09:53 +01:00
Carlos Florêncio 6ffb157d7e fix: user workflow deadlock (#677) 2026-04-23 09:54:06 +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 5f1c4a13e4 fix(web): enable Start task button when workflow provides agent override (#665) 2026-04-20 23:10:50 +01:00
Carlos Florêncio 6909fe6854 feat(web): session tabs on kanban preview panel (#648) 2026-04-20 22:31:09 +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
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
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
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
Francisco Magalhães cd46516cef fix: ux consistency on archiving and deleting tasks (#627) 2026-04-17 23:20:10 +01:00
Carlos Florêncio 814efdfa67 fix(cli): isolate dev mode state when running inside a kandev task (#617) 2026-04-17 19:41:29 +01:00
Carlos Florêncio cb8abfed78 fix(web): disable multi-select mode after bulk archive or delete (#623) 2026-04-17 19:36:19 +01:00
João Salavisa f440007efc feat: add confirmation dialog before archiving tasks (#621) 2026-04-17 18:27:10 +01:00
Francisco Magalhães 78d783237b feat: introduce card multi-selection (#573) 2026-04-17 18:16:39 +01:00
Carlos Florêncio 60614a78a9 fix(web): close file diff tab when uncommitted change is undone (#618) 2026-04-17 16:46:08 +01:00
Carlos Florêncio dc827a25d6 fix(github): treat skipped checks as passing and add ready-to-merge status (#616) 2026-04-17 16:33:43 +01:00
Carlos Florêncio 181a64a5fb fix: prevent utility agents settings page crash on null models (#602) 2026-04-17 14:22:34 +01:00
João Salavisa 4640bed408 fix(web): validate activeSessionId belongs to activeTaskId before use (#614) 2026-04-17 02:35:12 +01:00
Carlos Florêncio 99a8407dd8 fix: close clarification overlay when agent moves on (#608) 2026-04-16 15:58:01 +01:00
Carlos Florêncio 8ce34da1b7 fix(github): prevent duplicate review tasks via atomic PR reservation (#605) 2026-04-16 14:20:51 +01:00
Carlos Florêncio a222854422 feat: associate agent profiles with workflows and steps (#597) 2026-04-16 10:14:45 +01:00
João Salavisa f74de1c642 fix: improve plan comment formatting to match code review style (#600)
* fix: improve plan comment formatting to match code review style

- Update formatPlanCommentsAsMarkdown to use code blocks and blockquotes
- Remove duplicate header in chat-input-area.tsx
- Update buildDocumentContext to use consistent formatting
- Fixes duplicate '### Plan Comments' headers and extra separators

* fix: add tests and fix multiline blockquote handling

- Add format.test.ts for formatPlanCommentsAsMarkdown and related functions
- Fix multiline comment text to properly prefix each line with >
- Addresses review feedback about missing tests and broken blockquotes

* fix: export and reuse toBlockquote helper to avoid duplication

* fix: update E2E test to match new plan comment format

The test was expecting 'Comment 1:' which was the old bullet-style format.
Now looks for 'Plan Comments' header instead.
2026-04-16 02:57:41 +01:00
Luan Martins 59d7270056 fix(web): disable resume and show agent selector when profile is deleted (#578) 2026-04-15 22:31:02 +01:00
Carlos Florêncio 1f4451b192 fix(web): add confirmation dialog before deleting agent profile (#596) 2026-04-15 22:16:34 +01:00
Carlos Florêncio d3945b00ce fix(web): read session state at call time in comment run to prevent stale queue (#588) 2026-04-15 19:55:57 +01:00
Carlos Florêncio 204fac1cca feat: acp-first profiles, models, and modes (#566) 2026-04-15 13:22:32 +01:00
José Almeida 2b8513f902 feat: add multi-select and drag-to-move for file tree and changes panel (#490)
* feat: add multi-select and drag-to-move for file tree and changes panel

Add Shift-click range and Ctrl/Cmd-click toggle selection to the file
tree and staged/unstaged changes sections. Includes bulk actions
(delete, stage, unstage, discard), drag-and-drop file moving into
directories, keyboard shortcuts (Escape, Ctrl+A), and E2E tests.

* fix: move bulk action components to file-row to fix max-lines lint

* style: apply prettier formatting to all changed files

* fix: bulk delete all selected items and update anchor on stale shift-click

* fix: address CodeRabbit review comments

- Keep original file-row-* data-testid for backwards compat
- Remove eager onClearSelection from bulk actions (auto-pruned by hook)
- Variant-scope bulk-discard/bulk-unstage test ids
- Remove index.lock deletion from E2E GitHelper retry loop

* fix: fix E2E tests — create files after session open, remove DnD test

- Create files AFTER opening session so WS detects git status changes
- Add proper git env vars (GIT_AUTHOR_NAME, etc.) matching existing tests
- Use element.press("Escape") instead of page keyboard for proper focus
- Remove drag-to-directory test (Playwright dragTo doesn't support HTML5
  dataTransfer events needed by our DnD implementation)

* fix: rename E2E task titles to avoid ambiguous 'Changes' tab selector

* fix: make file list focusable for Escape key handler in E2E tests

* fix: improve selection styling and ignore right-clicks in selection handler

- Use text-accent-foreground on selected rows for dark mode readability
- Override child span colors with [&_span]:text-accent-foreground
- Use hover:bg-accent/80 instead of hover:bg-muted on selected items
- Skip selection handling on right-click (button === 2) so context menu
  works without clearing multi-selection

* fix: plain click should not select — only Ctrl/Cmd+click and Shift+click

Plain click now clears any selection and performs the original action
(open file / toggle folder / open diff). Selection only happens with
modifier keys. handleClick returns boolean to indicate whether the
click was consumed by selection logic.

* fix: fix multi-select bugs and expand E2E test coverage

Bug fixes:
- Change isSelected from boolean to function prop so children compute
  their own selection state (fixes nested file selection)
- Inline TreeNodeRow JSX into TreeNodeItem so ContextMenuTrigger asChild
  can attach refs directly (fixes right-click context menu)
- Set lastClickedRef on plain click so shift-click has a valid anchor
- Add click-outside handler to clear selection

E2E tests expanded to 14 file-tree tests + 3 changes-panel tests:
- Ctrl-click select, toggle, multi-select
- Plain click clears selection
- Shift-click range after plain click and ctrl-click anchors
- Directory selection and nested file selection
- Right-click context menu (single + bulk delete)
- Click outside clears selection
- Escape clears selection

* fix: move AlertDialog outside ContextMenu to fix delete action

The AlertDialog wrapping the ContextMenu caused Radix focus management
to intercept context menu item selection, making Delete trigger Rename.
Moving AlertDialog to a sibling of ContextMenu fixes the conflict.

* fix: reorder context menu — Delete first, Rename second

Radix ContextMenu auto-focuses the first item on open, which caused
accidental Rename triggers. Putting Delete first (the more common
action for selection) avoids this.

* fix: don't clear selection when clicking context menu or dialog

The click-outside handler was clearing selection when clicking on
context menu items or confirmation dialog buttons (rendered in portals
outside the tree container). Now skips clearing for elements with
role=menu, role=alertdialog, or role=dialog.

Also extended the bulk delete E2E test to verify both files are
actually removed after confirming the dialog.

* fix: hide file count in delete dialog when folder children not loaded

When a folder hasn't been expanded, its children aren't loaded yet so
countFilesInTree returns 0. Instead of showing "0 files", hide the
count entirely and just show "delete <folder name>".

* fix: show confirmation dialog for bulk discard in changes panel

Bulk discard was calling git.discard() directly, bypassing the
confirmation dialog. Now routes through handleBulkDiscardClick which
opens the DiscardDialog with a bulk message ("discard changes to N
files") before executing.

* fix: address remaining CodeRabbit review comments

- Deduplicate filenames on move: when moving files with identical names
  to the same directory, append "(1)", "(2)" etc. to avoid collisions.
  Both tree update and rename API calls use the same deduplication.
- Replace void operator with .catch(() => {}) for bulk stage/unstage
  to prevent unhandled promise rejections.
- Add document-level dragend listener as safety net to clear stuck drag
  state if the element's onDragEnd doesn't fire (Escape, out-of-window).

* test: add unit tests for file tree move deduplication and visible paths

* fix: address all remaining PR review comments

- Move onSelectionChange out of React state updater (side effect fix)
- Stabilize keyboard listener deps (pass callbacks not object)
- Remove unnecessary useMemo in BulkActionBar
- Fix ghost-restore on partial bulk delete (remove all at once)
- Fix blank filename on single-file bulk discard
- Add aria-selected and role=treeitem for accessibility
- Extract computeNextSelection params into object (max-params lint)

* fix: add missing bulk action props to WorkingTreeProps type

WorkingTreeSections referenced onBulkStage, onBulkUnstage, and
onBulkDiscard but WorkingTreeProps Pick didn't include them, causing
a TypeScript build failure.

* fix: address final review comments

- Add unit tests for computeNextSelection (8 tests covering ctrl-click,
  shift-click range, stale anchor, reverse range, shift+ctrl extend)
- Extract shared E2E helpers (GitHelper, makeGitEnv, openTaskSession,
  createStandardProfile) to e2e/helpers/git-helper.ts
- Fix misleading comment in bulk delete (says "re-insert only failed"
  but implementation restores full snapshot)

* fix: use Promise.all for bulk delete to prevent partial rollback

Individual .catch() handlers could restore already-deleted files
if a later delete failed. Now uses Promise.all so rollback only
happens if any delete rejects.

* test: add renderHook tests for useMultiSelect hook behaviors

- Stale-path pruning: items shrink → selected paths auto-pruned
- Plain click: returns false, clears existing selection
- selectAll/clearSelection: work correctly, fire onSelectionChange
- Install @testing-library/react for renderHook support

* fix: handle false returns from onDeleteFile in bulk delete Promise.all

* chore: remove unused @testing-library/react-hooks dependency
2026-04-11 17:43:21 +01:00
Carlos Florêncio 2ae996a2c1 fix(agentctl): register MCP tools with _kandev suffix to match sysprompt (#572) 2026-04-10 17:17:42 +01:00
Carlos Florêncio ebefac814a docs: enforce test requirements and improve agent skill resilience (#543) 2026-04-09 16:27:19 +01:00
Carlos Florêncio 5ff930c6e0 fix(web): keep file tree and terminal waiting through long prepare (#564) 2026-04-09 12:49:47 +01:00
Carlos Florêncio a8092fd0a9 fix(web): sort nested file tree folders before files (#562) 2026-04-08 19:41:07 +01:00
Carlos Florêncio 2c222a8e7a fix(web): scroll dropdown selectors inside dialogs (#559) 2026-04-08 18:46:31 +01:00
Carlos Florêncio 41b405c64f fix(backend): prevent task reorder during silent session resume (#555) 2026-04-08 07:55:24 +01:00
Carlos Florêncio 73e6ef64b4 fix(orchestrator): persist live git status snapshot for sidebar diff badges (#556) 2026-04-07 23:06:39 +01:00
Carlos Florêncio 61e5d5cce9 feat(web): redesign task sidebar with repo-grouped layout and diff stats (#550) 2026-04-07 09:12:17 +01:00
Carlos Florêncio 03c164d6cd fix: handle file paths with spaces in git status and diff parsing (#552) 2026-04-06 19:25:26 +01:00
Carlos Florêncio a44489640b fix: changed branch and auto focus changes panel (#549) 2026-04-06 18:59:37 +01:00
Carlos Florêncio 52d6f7e9bf fix(web): persist PR panel dismissal across page refreshes (#547) 2026-04-06 13:52:53 +01:00