* fix(linear): match issue identifier (ENG-123) in search
Linear's `searchableContent` filter never matches the issue identifier
(team key + number), so a query like `ENG-123` from the Linear view
returned no results. When the query parses as a `TEAM-NUM` identifier,
OR-combine the content match with `{team.key.eq, number.eq}` so the
exact issue surfaces while other filters keep AND-joining. Lowercase
inputs (`eng-123`) are normalized to the upper-case team key.
Fixes#1339
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(linear): mention ID in search box placeholder
Reflect the backend's new identifier-aware search by hinting at it in
the search input on `/linear`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(linear): explain why identifier search keeps both OR branches
claude[bot] flagged the searchableContent branch in the identifier OR
as functionally dead and the surrounding comment as a sentence fragment.
The branch is intentional — it catches cross-references like "duplicate
of ENG-123" pasted into another issue's title or description, which the
exact team+number branch would miss. Rewrite the comment to explain
that, fixing the fragment in the same change.
* fix(linear): use canonical identifier form in searchableContent OR branch
greptile flagged that the `searchableContent.contains` branch in the
identifier OR was sent verbatim from user input. When the user typed
`eng-123`, Linear's case-sensitive `contains` filter would miss
cross-references written in the canonical form `ENG-123`. Normalise
the contains value to the same upper-cased identifier we use for the
exact `team+number` branch, and lock that behaviour in
`TestBuildIssueFilter_IdentifierLowercase`.
* fix(linear): use containsIgnoreCase for searchableContent
@carlosflorencio flagged that canonicalising the identifier OR branch
to upper case meant lowercase literal references (e.g. "see eng-123"
pasted into a description) no longer matched. The previous round had
swapped the other way at greptile's request to fix uppercase
references. Both directions are right — the underlying issue is that
Linear's `contains` filter is case-sensitive. Switch the whole
`searchableContent` filter (identifier OR branch and the non-identifier
free-text path) to `containsIgnoreCase`, so casing of the user input
and of the indexed text no longer matters in either direction.
Free-text search is now case-insensitive too, which is the intuitive
default for a search box.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(task): cascade-archive child tasks on workflow delete
Tasks pointing at a deleted workflow_id silently became unreachable
because the FK on tasks.workflow_id was dropped to support ephemeral
quick-chat tasks, so SQLite cannot CASCADE. Service.DeleteWorkflow
now archives every active child task before removing the workflow,
re-using the existing ArchiveTask path (events, worktree cleanup,
agent stop). Frontend confirm copy and button label updated to reflect
the new behavior.
Closes#1279
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(task): make workflow-delete cascade race-safe
Address review feedback (Greptile, Claude, cubic): ArchiveTask now returns
a typed ErrTaskAlreadyArchived sentinel so DeleteWorkflow can swallow the
TOCTOU race where a task is archived between ListTasks and ArchiveTask.
Without the sentinel, that race would propagate as an opaque error and
leave the workflow row intact even though every task in the snapshot was
already in the desired state.
Tests:
- TestService_DeleteWorkflow_SkipsConcurrentlyArchivedTask covers the
sentinel-skip path through the cascade.
- TestService_DeleteWorkflow_PartialArchiveErrorPreservesWorkflow covers
the fail-fast contract on a real (non-sentinel) error: workflow row
survives, previously-archived tasks stay archived, retry is safe.
- TestService_ArchiveTask_ReturnsAlreadyArchivedSentinel locks in the
sentinel-error contract DeleteWorkflow relies on.
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>
* feat(stats): add avg turn duration and messages per turn to averages card
Adds two new metrics to the workspace stats Averages card: mean turn
duration and mean message count per turn. Both exclude outliers — turns
shorter than 1s (no-op/aborted), longer than 1h (zombie turns where
completed_at was backfilled across an agent restart), and turns with no
messages — so the displayed means reflect typical work rather than tail
skew. On a ~4k-turn sample the filter drops avg duration from ~1062s to
~289s.
* fix(stats): exclude exact 1h turns and use Tooltip for averages help text
Address PR feedback: the BETWEEN bound was inclusive so turns exactly at
the 1h ceiling were retained, contradicting the documented half-open
range. Also swap the title attribute on the new Averages rows for the
shared Tooltip component so the help text is reachable via keyboard and
screen readers.
* fix(stats): align turn-row zero state with em-dash convention
Address PR feedback: avg_messages_per_turn rendered "0.0" while
avg_turn_duration_ms rendered "—" under the same empty-data condition.
Also corrects the GetGlobalStats docstring to acknowledge that
clean_turn_agg adds one correlated per-turn lookup on top of the four
single-scan CTEs.
* test(stats): cover exact 1h turn boundary in clean-turn exclusion test
Add a turn of exactly 3,600,000ms (cleanTurnMaxDurationMs) to
TestGetGlobalStats_CleanTurnExcludesOutliers so the exclusive upper
bound is exercised directly, not just via the 2h zombie turn that
sits well above the boundary.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
* fix(web): collapse dockview workflow stepper to current step when cramped
On narrow windows the full workflow step indicator outgrew the top-bar
center track and rendered beneath the right-side action buttons. Switch
the grid so the breadcrumb takes only its content width and the center
track absorbs the real leftover space, then collapse the stepper to a
minimal current-step view (via the shared useToolbarCollapsed hook) only
when the full version genuinely cannot fit.
* fix(web): stabilize stepper test id and drop margin skewing collapse
Move data-testid="workflow-stepper" to the persistent outer container so
the e2e locator resolves in both expanded and collapsed modes, and remove
mx-2 from the w-full measuring container (it overflowed the wrapper and
shifted the collapse threshold by ~8px); the grid gap-2 already spaces the
columns.
* refactor(web): trim verbose stepper collapse comment
* fix(web): tag archived minimal badge with stepper test id
Give the archived branch of MinimalWorkflowStepper the same
workflow-stepper-minimal test id as the current-step branch so collapsed
mode is detectable for archived tasks, and tighten two comments.
* test(web): collapse multi-line comments in stepper test to single lines
* fix(web): drop aria-current on minimal stepper no-current fallback
When no step is current the minimal view falls back to the first step;
it should not claim aria-current="step", matching the expanded path where
no step is marked current in that case.
* fix(web): keep dockview group alive on task switch to stop layout collapse
On a fast-path env switch, the incoming session chat was added only after
removeEphemeralPanels closed the outgoing chat. When that chat was the last
surviving panel in its group (e.g. the default layout's chat column), removing
it first emptied — and so destroyed — the group. The captured group id then no
longer existed, and post-#1165 the old `referenceGroup: "sidebar"` fallback is
dead, so addPanel ran with an undefined position: dockview dropped the incoming
chat into the active (terminal) group and collapsed the grid root to a vertical
stack. The corrupted layout was then persisted, so it survived reloads.
Add the incoming session panel before removing the outgoing one, anchored at the
outgoing panel's current index, so the group stays alive throughout the swap.
Removing the panels ahead of the insert point shifts the new panel into the same
final slot, so computeSurvivingIndex is no longer needed.
* docs(web): note the raw-index equivalence assumption in env switch
* fix: gate step_complete_kandev tool on per-step signal flag
Two ADR-0015 follow-up bugs:
- "Wait for agent completion signal" checkbox snapped back because
updateWorkflowStepAction dropped auto_advance_requires_signal from the
PUT body. Add it to the Pick<>, body builder, BackendWorkflowStep, and
transformWorkflowStep.
- step_complete_kandev MCP tool was injected into every first-turn
prompt, polluting context and tempting agents on non-signal-gated
steps to call it. Replace the static description in
kandev-context.md with a {step_complete_section} placeholder filled
only when the current step has auto_advance_requires_signal=true.
Add orchestrator.Service.StepRequiresCompletionSignal helper and
thread it through all four InjectKandevContext call sites.
* fix: address PR 1300 review feedback
- Hoist ProcessOnTurnStart above the first-turn MCP wrap so an
on_turn_start step transition is reflected in the
auto_advance_requires_signal flag baked into the stored/forwarded
prompt (coderabbit, cubic).
- Add Service.WorkflowStepRequiresCompletionSignal step-ID variant and
use it from task_operations.go / autoStartStepPrompt to skip the
redundant GetTask round-trip at hot first-turn launch sites (claude).
- Add table-driven tests covering every silent-false branch and the
positive path of both helpers (greptile, claude, cubic).
* docs(sysprompt): document stepCompleteSection trailing newline contract
Address Claude review suggestion on PR 1300: make explicit that the
constant must end with "\n" because the kandev-context.md template inlines
{step_complete_section} immediately before the next bullet, so the
trailing newline is what separates the two list items.
* fix: restore task-id lookup at startTask wrap site
Address PR 1300 review regression: the previous swap to
WorkflowStepRequiresCompletionSignal(ctx, workflowStepID) silently hid
step_complete_kandev on manual starts where the workflowStepID parameter
is empty but the task is already bound to a signal-gated step in the DB.
The `task` variable in scope at this call site is `*v1.Task` (from the
scheduler) and does not carry WorkflowStepID, so the cheaper step-ID
variant is not applicable here — go back through StepRequiresCompletionSignal
so the repo lookup pulls the canonical step.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>