Commit Graph

27 Commits

Author SHA1 Message Date
Naiyuan Qing
3ce4cf6f2f fix(lists): navigate rows via onClick, not a nested row anchor (#4146)
Clicking a row's ⋯ kebab (or any in-row control) full-page reloaded the
app. The row was a whole-row <AppLink>, so a child's stopPropagation
stopped the event before AppLink's onClick (which calls preventDefault to
cancel native anchor navigation and do an SPA push) could run — leaving
the browser to perform the native <a> navigation, i.e. a full reload. It
was also invalid HTML: interactive content (button/menu) nested in an <a>.

Rework all five ListGrid row surfaces (agents, runtimes, skills,
autopilots, squads) to a plain <div> row whose whole-row navigation is a
mouse onClick (new useRowLink hook): left-click pushes, cmd/ctrl/middle
opens a background tab. Interactive cells (checkbox, kebab) stopPropagation
so they never trigger row nav — and with no <a> ancestor there is no native
navigation to cancel, so the reload class of bug is gone. Names are plain
text since the row itself is the click target. projects is unchanged — its
inline-editable cells make it a deliberate name-link exception.

Also fixes two adjacent defects found in the same menus:
- agents/runtimes kebab triggers reused the shared <Button>, which lacks
  the data-popup-open styling the other surfaces have, so the trigger
  vanished and lost its background while its menu was open. Switch them to
  the bare-button trigger with data-popup-open: visible + highlighted.
- agents archive menu items used className="text-destructive" instead of
  variant="destructive", so the base focus style overrode the red on hover.
  Switch to variant (list row + detail page).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:56:38 +08:00
Naiyuan Qing
63cf0ed308 feat(lists): rebuild all six list surfaces on a shared Linear-style list grid (#4038)
* fix(issues): render thread replies in chronological order (#3691)

collectThreadReplies walked the parent_id tree depth-first, so an agent
reply forced to nest under its trigger comment rendered before earlier
sibling replies (A-D-B-C instead of A-B-C-D) whenever the agent returned
late. Sort the collected subtree by created_at (id tie-break) so the
thread reads in arrival order — the same order the server already feeds
agents via `comment list --thread` (ListThreadCommentsForIssue).

All other consumers of the array (resolution derivation, fold bars,
counts, deep-link) are order-independent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills): rebuild skills list on shared Linear-style list grid

- new ListGrid primitives (subgrid: single source of truth for column tracks)
- skills list: sortable columns, used-by avatar stack, source/creator columns,
  row kebab + batch toolbar with add-to-agent and delete
- skill view store in core; addAgentSkills client method; HoverCheck extracted
  to views/common (issues header now imports the shared copy)
- locale keys for list actions/filters and the reworked detail page

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills): rework detail page into overview/files tabs

- tabs directly under the breadcrumb header: overview (default) and files
- overview: identity block + rendered SKILL.md as the main column, right
  rail with metadata card (source/creator/updated, inline name+description
  edit toggle) and used-by panel with bind/unbind
- files: file tree + viewer/editor unchanged; SKILL.md "edit" jumps here
- header kebab menu (copy skill ID, delete); page-level save bar shared by
  both tabs; tab state persisted in ?tab=
- file tree: ARIA tree roles + roving-tabindex keyboard navigation
- drop the old right sidebar (metadata dl, permissions paragraph)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* revert(skills): restore detail page to main, keep branch list-only

Drop the overview/files tabs rework from this branch so the PR scope is
the list rebuild only. skill-detail-page.tsx and file-tree.tsx are back
to the main versions; the locale detail/file_tree sections are restored
to match. The detail rework is preserved on stash/skills-detail-tabs
for a follow-up PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills): drop description column from skills list

Description is agent-facing routing metadata, not a scannable list
property — Linear's display options expose no description column for
the same reason. Removes the cell, column key, display toggle, lg grid
track, skeleton cells, and the now-dead table.description /
table.no_description locale keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills): drive list column hiding by container width, drop by priority

Replace viewport sm:/lg: breakpoints with Tailwind v4 container query
variants (@2xl/@4xl) on the list wrapper, so an open sidebar or split
pane narrows the column set instead of squashing tracks. Remove the
min-w-fit + overflow-x-auto horizontal-scroll fallback: when space runs
out, low-priority columns (created/source/creator, then updated) drop
and return as the container widens; name and usedBy never drop. ListGrid
conventions comment updated — this is the template for all list pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills): virtualize list rows with @tanstack/react-virtual

Linear-style headless virtualization: the virtualizer computes the
visible index range and offsets; offsets land as padding on the
scrolling ListGridBody so mounted rows stay direct subgrid children and
column alignment is untouched. Fixed 48px rows skip per-row measurement.

Hideable column tracks move from max-content to deterministic widths
(CSS vars) — with only the visible slice mounted, content-driven tracks
would resize during scroll. A user-hidden column zeroes its var so the
track still collapses; per-cell max-w caps move into the tracks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(skills): list tiers must fit their container trigger width

The @4xl tier's track sum (~1080px with gaps) exceeded its 896px
trigger; with the horizontal-scroll fallback gone, the right-side
columns were clipped unreachably between 896-1080px. Move tier 3 to
@5xl (1024px), trim usedBy/source/creator tracks, and document the
fit invariant with its arithmetic next to the template and in the
ListGrid conventions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills): show description as subtext under the skill name

Lives in the name track as a second truncated line (max-w 36rem,
title attr for the full text) — no track, no header, no slot in the
responsive arithmetic. Both lines fit the fixed 48px row, so the
virtualizer contract is untouched; rows without a description center
the name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Revert "feat(skills): show description as subtext under the skill name"

This reverts commit f39721301b.

* fix(skills): anchor batch toolbar to the page, not the viewport

fixed bottom-6 left-1/2 centered the bar on the window; with the
sidebar open the list's visual center sits ~120px right of the window
center, so the bar looked off-center (worse with desktop split panes).
Page root becomes the positioning context (relative) and the bar uses
absolute — same rule applies to future list pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills): show matching count next to search while list is narrowed

"n / total" appears right of the search box only when search or
filters are active — idle state would duplicate the total already in
the page header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(autopilots): derive trigger kinds, next run, last run status in list

The list endpoint only selected the autopilot table, so the list UI
could not answer "is this automation working" without N+1 detail
calls. Each list row now carries trigger_kinds + next_run_at (enabled
triggers only — the columns describe how it fires today) and
last_run_status (most recent run). Fields are omitempty and absent
from detail/create/update responses; clients must treat them as
optional per the API compatibility rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(autopilots): list schema, parsed client, and view store in core

- listAutopilots now runs through parseWithFallback with a zod schema
  (this endpoint was a bare fetch — overdue per the API compatibility
  rules); malformed bodies degrade to an empty list, old-server rows
  without assignee_type or the new derived fields parse cleanly, and
  enum drift passes through as plain strings
- Autopilot type gains the three optional list-only derived fields
- New autopilots view store (scope/sort/columns/filters, persisted per
  workspace): status is the promoted scope dimension so it does NOT
  appear in filters — one dimension lives in exactly one place

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(autopilots): rebuild list on shared ListGrid with scope buttons

Same skeleton as the skills list (container-query tiers, deterministic
var-width tracks with documented fit arithmetic, virtualized 48px rows,
sortable headers, filter + display toolbar, page-anchored batch
toolbar), plus the autopilots-specific pieces:

- Status is the promoted SCOPE dimension: 全部/运行中/已暂停/已归档
  segmented buttons with full-set counts; "all" = active+paused
  (archived gets its own visible home, Linear archive semantics);
  status is therefore absent from the filter dropdown
- Columns: name (paused marker inline), assignee (agent/squad),
  trigger kind badges, last run (outcome dot + time, enum-drift safe
  default), next run; mode/creator/created opt-in hidden
- Filters: assignee, trigger kind, mode, creator (composite type:id
  values for polymorphic actors); sort name/lastRun/nextRun/created
  with lastRun desc default
- Row kebab (pause/resume/archive/unarchive/delete) and batch toolbar
  share one delete dialog; status changes ride useUpdateAutopilot's
  optimistic cache
- Fix noUncheckedIndexedAccess errors the branch had never typechecked
  (skills virtual rows, UsedByCell, added_toast)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(autopilots): scope buttons follow the issues header pattern

Replace the bespoke segmented-pill control with the existing scope
button convention from the issues page: outline buttons with bg-accent
active state on md+, collapsing to a radio dropdown below md. Counts
stay (stage inventories from the full set).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(skills,autopilots): toolbar small-screen treatment follows issues header

Below md: the search box (and its result count) disappear entirely,
and the filter/display controls collapse to square icon-only buttons
(labels and the clear-X are md+), matching the issues header's
responsive pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(skills,autopilots): two-zone columns — WYSIWYG with scroll escape valve

Static width tiers silently hid user-enabled columns (toggle on,
nothing appears — autopilots' mode/creator/created sat behind a 1280px
container gate no laptop reaches; skills' source/created behind
1024px). Tiers can't know how many columns are enabled, so the
mechanism is replaced, not retuned:

- ≥@2xl container: every enabled column renders; the grid carries
  min-width = Σ(enabled tracks + gaps) (pure constants, no
  measurement) and the wrapper scrolls horizontally only when the
  enabled set outgrows the container
- <@2xl: static core set (skills: name+usedBy; autopilots:
  name+assignee), no scroll, toggles don't apply

Per-tier templates and the hand-maintained fit arithmetic retire;
ListGrid conventions updated accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(skills,autopilots): widen name column minimums (120px base, 200px wide)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(autopilots): drop the archived scope and the list search box

Archiving never existed as a UI flow (the DB status value is only
reachable via direct API; the detail page disables its switch when
archived), so the list stops inventing it: no archived scope, no
archive/unarchive row or batch actions. API-archived rows are excluded
everywhere; a persisted retired scope value falls back to "all".
The search box goes too — scope buttons already partition the small
set, search is redundant (product call). Skills keeps its search (no
scope there).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skills,autopilots): quiet outline create buttons in page headers

Page-header chrome shouldn't carry the loudest element on the page:
the create button becomes outline with text on md+ and collapses to a
square plus icon below md (same responsive treatment as the toolbar
controls). Primary stays reserved for empty-state CTAs. Agents follows
when its list migrates to ListGrid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(agents): rebuild list on shared ListGrid with identity rows

Same skeleton as the skills/autopilots lists (two-zone container
responsiveness, deterministic var tracks + min-width scroll escape
valve, virtualized fixed-height rows, issues-style scope buttons,
page-anchored batch toolbar, quiet outline create button), plus the
agents-specific decisions:

- Identity rows: the documented exception to the single-line rule —
  avatar + name + description two-line cells, 64px rows (agents are
  few, identity-rich entities); the italic "no description"
  placeholder is gone, empty descriptions just center the name
- Scope: Mine (historical default) | All | Archived with full-set
  counts; archived ignores the ownership lens; no search box
- The 7d sparkline column is replaced by a sortable "Last active"
  column derived from the same 30-day activity buckets (zero API
  change) — per-row-normalized mini bars can't be compared across
  rows, and the default sort finally has a visible anchor; the
  detailed histogram stays on the hover card / detail page
- Workload folds into the status cell ("Online · 2 tasks") — a 0-2
  integer doesn't earn a column
- Columns: status, runtime, last active, runs (30d); model/created
  opt-in hidden; filters: availability, runtime
- Operations unchanged: row kebab reuses AgentRowActions
  (cancel-tasks/duplicate/archive/restore with permissions); batch
  archive (confirmed) + restore; no delete — the API has none
- View store extended (scope incl. archived, sort, columns, filters);
  agent-columns.tsx (DataTable columns) deleted

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(agents): trim status track to its real worst case (160 -> 144px)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(runtimes): machine detail's runtime table on the shared ListGrid

The master-detail console keeps its shape (machines are few and
strongly categorized; left list, charts, update section untouched) —
only the right pane's runtimes table moves from TanStack DataTable to
the ListGrid family, taking the paradigm pieces that earn their keep
at 1-5 rows: subgrid template + var tracks, two-zone container
responsiveness (the pane is squeezed by the machine list, so the
core-set collapse below @2xl matters more here than on full-width
pages), min-width scroll escape valve, shared header/row/hover visual
language. Deliberately NOT taken: virtualization, sorting, filters,
column toggles, and batch selection — dead weight at this row count,
and batch-deleting runtimes (a cascade-confirm operation) is unsafe
by design.

Workload folds into the health cell ("Online · Working 2") like the
agents status cell; the owner column keeps its only-when-multiple-
owners rule via a zeroed track var. runtime-columns.tsx is deleted;
the row-menu/CLI tests render the exported cells directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(runtimes): collapse the kebab track when no row has actions

On a healthy local machine every row's only action (delete) is hidden
by the self-healing rule, leaving a permanent ~64px dead zone after
the CLI column. The action track now follows the owner column's
conditional-var mechanism: zeroed unless at least one row will show
the menu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(runtimes): drop doubled header border, align create button with convention

PageHeader already carries border-b; the content wrappers' border-t
stacked a second line right under it (the only list page doing this).
"Add a computer" follows the chrome-button convention: outline with
text on md+, square plus icon below md — primary stays reserved for
the empty state CTA.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(runtimes): health cell load suffix matches the agents status cell

"Healthy · 2 tasks" instead of the old workload vocabulary
("Working 2 +1q") — the count is unit-bearing and both surfaces now
speak one language. The queued-anomaly distinction the old words
hinted at belongs to the health layer if it ever earns surfacing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lists): pin overflow-y-hidden on the horizontal-scroll wrappers

CSS coerces overflow-x:auto into overflow:auto on both axes, which
silently armed the list wrappers with a vertical scrollbar they were
never meant to have. Combined with the h-full grid's percentage
resolution across scrollbar-induced reflows, the wrapper's vertical
bar and horizontal bar fed each other in a non-converging layout loop
(visible as two stacked, flickering scrollbars on the agents list —
the same latent loop exists in all four wrappers; agents' wider
min-width and 64px rows just hit the trigger zone first). Vertical
scrolling belongs solely to ListGridBody; declare overflow-y-hidden
explicitly to break the loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(agents): single scroll container for the list (trial before rollout)

Both scroll axes move to the outer wrapper; the grid drops h-full and
the rows wrapper drops its own overflow. Kills the percentage-height
bridge between the two scroll elements that fed the flickering double
scrollbars and clipped the last row under the horizontal scrollbar.
Sticky header pins inside the scroller; vertical scrollbar now spans
the full pane (Linear's structure). Skills/autopilots follow after
visual confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lists): roll single scroll container out to skills/autopilots, add bottom clearance

ListGridBody retires its own scrolling entirely (the agents trial
confirmed the structure): both axes live on the single outer wrapper,
grids drop the h-full percentage bridge, virtualizers point at the
wrapper. The rows wrapper gains LIST_GRID_BOTTOM_CLEARANCE (64px)
appended to the virtualization padding so the last row scrolls clear
of the chat FAB (~48px at bottom-right) and the batch toolbar (~62px).
Runtimes' machine table is untouched: content-height at the top of a
tall pane, no bridge and no practical FAB overlap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(squads): rebuild list on shared ListGrid (identity rows, minimal)

The last list joins the family. Squads are the fewest entity (1-5 rows),
so this is the agents identity-row shell on the runtime-list minimal
skeleton: ListGrid subgrid + var tracks + two-zone responsiveness +
single scroll container, but NO virtualization, checkbox, or batch.

- Identity two-line rows (squad avatar + name + description, 64px) like
  agents; columns: name / leader / members (polymorphic ActorAvatar
  stack from member_preview), creator + created opt-in hidden
- Scope Mine/All (creator-based, issues-header styling, <md dropdown);
  no archived scope (list API hard-filters archived + no restore
  endpoint), no search (scope-bearing), no filters (set too small)
- Sort name (default) / members / created
- Row kebab = Archive (= the delete endpoint, which archives + transfers
  issues/autopilots to the leader); workspace owner/admin only, so the
  kebab track collapses for non-admins. Reuses the existing
  archive_dialog copy. No batch.
- View store extended (scope + sort + columns); zero API change — pure
  frontend (member_preview/count already in the list payload)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(agents,squads): owner/created-by columns + owner filter

Surface ownership as a real column on both lists, named by what the
field actually means in each permission model:
- Agents: "Owner" — owner_id is the creator (set at creation, never
  transferred) and carries management rights. Promoted to a default-
  visible column (avatar + name); the half-baked inline owner avatar in
  the name cell is removed ("You" badge stays).
- Squads: "Created by" (NOT Owner) — creator_id holds no rights
  (archiving is workspace-admin only), so Owner would mislead. Now a
  default-visible column with avatar + name.

Agents also gains an Owner filter, kept orthogonal to the Mine scope by
the single-axis rule: "Mine" is the clean no-filter personal view, so
applying any filter (owner or otherwise) leaves Mine for All, and
clicking Mine clears all filters. Owner and Mine therefore never
coexist — no "mine + owner=someone-else = empty" contradiction. Squads
keep the plain Mine/All toggle (too few rows for a creator filter).

Both lists keep a Created (date) column, opt-in hidden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(agents): backfill new filter dimensions on rehydrate (owners crash)

A view payload persisted before the owners filter existed overwrote the
default filters wholesale on rehydrate, dropping filters.owners to
undefined and crashing the list's filter predicate (.length on
undefined). The store merge now deep-merges filters over
EMPTY_AGENT_FILTERS so newly-added dimensions always get their default.
Regression test added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(skills,autopilots): deep-merge filters on rehydrate too

Same latent crash the agents store just hit: the copied view-store
merge spread persisted.filters wholesale, so adding a new filter
dimension later would drop it to undefined for users with older
persisted state. Harden skills and autopilots the same way (merge over
their EMPTY_*_FILTERS) before that bug can ship.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(projects): rebuild table view on ListGrid + filters + pin/delete kebab

Projects is the dual-view list: the compact table moves onto the shared
ListGrid (subgrid tracks, two-zone responsiveness, single scroll
container, FAB bottom clearance) while the comfortable card grid stays
as the alternate view, toggled by a restyled view switch (Table/Cards
outline buttons, active = bg-accent). Inline editing is preserved —
rows are NOT whole-row links; the name navigates and status/priority/
lead stay click-to-edit (matching prior behaviour, no navigate-vs-edit
conflict).

- View store extended: viewMode + sort (name/priority/status/progress/
  created) + hidden columns + filters (status/priority/lead); merge
  deep-merges filters (migration-safe). No scope (lead optional/often
  an agent; status is a 5-value lifecycle → filter, not scope).
- Toolbar: search (kept — scopeless list) + result count + Filter
  (status/priority/lead) + Display (sort+columns, table view only).
- Row kebab: Pin/Unpin (any member, reuses the existing project pin
  API — zero new endpoints) + Delete (workspace admin). Pin is the
  flexible per-user favourite the list previously lacked.
- Zero API change; status/priority filtering is client-side like the
  other lists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(projects): GRID_COLS must be a literal string (Tailwind can't see interpolation)

The table view's grid-cols template interpolated ${STATUS_WIDTH}px, so
Tailwind never generated the arbitrary-value class — the grid collapsed
to one column and every cell stacked vertically. Inline the literal
116px. This is the documented ListGrid rule (keep the class literal so
Tailwind scans it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(projects): single view-toggle button, decouple Display from view mode

Two fixes from the same principle — view mode is pure presentation and
must not couple to anything:
- The view switch is now ONE button that flips table ⇄ cards (shows the
  current view's icon+label, tooltip names the target), instead of two
  side-by-side buttons.
- The Display (sort/columns) control no longer disappears when you
  switch to cards — it was gated on isCompact, so flipping the view
  made it vanish (the "filter gone after switching" weirdness). It's
  always present now; only the columns *section* inside the popover is
  table-only (cards have no columns). Sort applies to both views.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(projects,squads): projects multi-select + squads FAB clearance/toast

Cross-list consistency audit fixes:
- projects: add multi-select (checkbox column + select-all header +
  page-anchored batch toolbar) — it's a dozens-scale full-page list
  like skills/autopilots/agents but was the only one missing it. Batch
  ops: Pin all (any member) + Delete (workspace admin). Table view
  only (cards have no checkboxes). GRID template + min-width updated
  for the checkbox track.
- squads: add the FAB bottom clearance the other full-page lists have
  (last row/kebab was sliding under the chat FAB).
- squads: archive success toast was showing the dialog's question
  title ("Archive this squad?"); use a proper "Squad archived" key.

Intentional and left as-is (documented): squads/runtimes have no
multi-select/virtualization (1-5 rows); projects table isn't
virtualized yet (dual-view + card grid; tracked as low-risk debt).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(agents,squads): close the filter/column consistency gaps

Apply the principle "every categorical column is filterable" where it
was missing:
- agents: add a Model filter (model was a categorical column with no
  filter). Distinct non-empty models from the in-scope rows.
- squads: add filters entirely (it had leader/creator columns + a
  column-toggle panel but no Filter button — the only such outlier).
  Leader (agent) + Creator (member) filters, with the result count and
  the same Filter dropdown shape as the other lists. Store gains
  SquadListFilters + toggleFilter/clearFilters + migration-safe
  filters deep-merge.

autopilots creator stays default-hidden per product call (not every
"who made it" must be visible). Filter stores' partialize tests
updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(autopilots): match list-page root to flex-1 convention

skills/agents/projects roots use `relative flex flex-1 min-h-0 flex-col`;
autopilots used `h-full`. Both anchor the batch toolbar correctly, but
align the flex sizing for consistency across the six list surfaces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 14:12:24 +08:00
Bohan Jiang
03961206ff docs(squad): correct stale "four status buckets" comments to five (#3640)
Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-02 13:19:14 +08:00
YYClaw
a6b83fef41 fix(agents): surface archived status for retired agents (#3608)
Retired agents (agent.archived_at set) previously read as offline across
the agent dot, hover card, detail badge, and squad member list — a
leftover online runtime row could even make them look reachable. Add a
dedicated archived presence/status that wins over every runtime/task
signal so a retired agent never reads as live or merely offline.

- Add archived to AgentAvailability and SquadMemberStatusValue unions
- Short-circuit deriveAgentPresenceDetail before runtime/task scan
- Backend deriveSquadMemberStatus returns archived instead of offline
- Render gray Archive dot/label; skip workload + reassign affordances
- en/ko/zh-Hans locale strings
2026-06-02 13:03:15 +08:00
Naiyuan Qing
645af40ed9 refactor(views): unify detail/list headers into shared BreadcrumbHeader (#3510)
* refactor(views): unify detail/list headers into shared BreadcrumbHeader

Replace four hand-rolled, divergent header styles (workspace-name root,
"/" separator, back-arrow, raw div) with one shared BreadcrumbHeader
component. The mental model is now identical everywhere: leading crumbs
are the thing's real containers and clicking one navigates up.

- New packages/views/layout/breadcrumb-header.tsx (segments/leaf/actions)
- Detail pages (issue, project, runtime, skill, autopilot, agent, squad)
  now render `{Section} › name`; org name removed as a breadcrumb root
- Issue breadcrumb shows the single most-direct container only (parent
  wins over project; they are orthogonal columns), never a fabricated
  chain; bare issue shows just its title
- Issue leaf (identifier + title) is now a clickable link to the issue
  detail page with a subtle hover:opacity-80
- Issues / My Issues list headers drop the workspace prefix, matching the
  icon + title style of the other list pages

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(views): update breadcrumb tests for unified header behavior

The header unification changed three observable behaviors the tests
asserted against:
- issue detail no longer renders the workspace name as a breadcrumb root
- bare issue shows only its (now clickable) title leaf, no ancestor crumbs
- the project "Unknown project" error placeholder was removed

Rewrite the two affected issue-detail tests to assert the new leaf-link
and no-project-crumb behavior, drop the obsolete Unknown-project test, and
update the issues-page header test to assert the workspace prefix is gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 14:27:51 +08:00
Alex
746c0c4456 MUL-2746 fix(avatar): normalize relative avatar urls in desktop/web (#3100)
* fix(avatar): normalize relative avatar urls in desktop/web

Co-authored-by: multica-agent <github@multica.ai>

* fix: test

Co-authored-by: multica-agent <github@multica.ai>

* fix(avatar): normalize avatar url in AvatarPicker preview

MUL-2746. The picker is used by create-agent and create-squad, and also
prefills from a template's `avatar_url` when duplicating an agent. The
upload result / template URL is root-relative in local-storage setups,
so on Desktop (file:// runtime) the preview <img> resolves against the
local filesystem and the avatar fails to render. Route the value through
`resolvePublicFileUrl` for rendering only; the stored URL stays raw so
the parent's create call still posts what the backend expects.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: J (Multica agent) <agents@multica.ai>
2026-05-27 16:02:08 +08:00
Naiyuan Qing
6261ea45fd Improve board and squad hover cards (#3188) 2026-05-25 12:58:39 +08:00
Naiyuan Qing
f0c32d5728 fix(views): move member count from header badge to section label in squad popover (MUL-2586) (#3178)
Remove the standalone member-count badge from the squad profile card
header and display the count inline with the Members section label
(Members · N). Add max-height + scroll guard on the member list to
prevent card overflow with many members.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-25 09:32:16 +08:00
Naiyuan Qing
44ee74eb25 feat(views): add squad popover hover card to ActorAvatar (#3176)
Squad avatars now show a hover card on dwell, matching the existing
agent and member cards. The card displays the squad name, member count
badge, description (line-clamp 2), and a members list (top 3, leader
first) with agent status dots. Clicking an avatar navigates to the
squad detail page. Closes MUL-2586.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-25 08:59:10 +08:00
Naiyuan Qing
e0b756f515 feat(issues): redesign board card layout + extract useTimeAgo i18n hook (#3064)
* refactor(views): replace static timeAgo with shared useTimeAgo hook

The previous timeAgo helper in packages/core/utils.ts hardcoded English
output ("2d ago"), producing "更新于 2d ago" mixed-language strings in
zh locale. Replaced with a localized useTimeAgo() hook in
packages/views/i18n, backed by common.time.{just_now,minutes_ago,
hours_ago,days_ago} translation keys. Migrated all 10 view-side
call sites and removed the static function.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(issues): redesign board card layout

Properties were piling onto the bottom row (assignee + priority badge
+ start date + due date) until it overflowed. Restructured into four
semantic rows:

- Top: priority icon (left, icon-only — color already conveys urgency)
  + identifier; agent activity indicator (right)
- Title
- Chip row: project + labels
- Meta row: assignee (left, avatar + name when only property present;
  bare avatar otherwise) + start/due dates + child progress

Long agent/team names truncate cleanly (min-w-0 + max-w-[160px]) and
dates/progress are shrink-0 so they never compress. When the meta row
contains only an assignee, the right side fills with "Updated 2d ago"
to avoid a half-empty row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:21:10 +08:00
Naiyuan Qing
317bca40c1 feat(squads): show skeleton on squad detail initial load (#2930)
Replaces the plain "Loading..." text fallback in SquadDetailPage with a
skeleton that mirrors the loaded page's two-column layout (left inspector
+ right tabs panel), matching the SquadsListSkeleton work shipped in #2890.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-20 17:21:52 +08:00
Naiyuan Qing
b040165f4e feat(squads): skeleton loader + AlertDialog archive confirm (MUL-2437) (#2890)
* feat(squads): skeleton loader + AlertDialog archive confirm (MUL-2437)

- Replace `Loading...` text on the squads list with a Skeleton placeholder
  matching the SquadCard shape (avatar + title + subtitle), aligning with
  the Agents / Dashboard pattern.
- Replace the native `confirm()` on the squad detail Archive button with
  the project's AlertDialog (destructive variant, pending-disabled, i18n
  copy interpolating the squad name).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(squads): drop misleading restore copy from archive confirm (MUL-2437)

Archive is irreversible — there is no unarchive command (see
apps/docs/content/docs/squads.mdx:113). Aligns dialog copy with
docs: tells the user the action can't be undone and to create a
new squad if they need the routing back.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-20 16:43:58 +08:00
Anderson Shindy Oki
e65c0889b9 feat: Add squad page responsive layout (#2826) 2026-05-19 09:18:30 +08:00
Jiayuan Zhang
46c1e2c889 feat(squads): show member working status on squad detail page (#2768)
* feat(squads): show member working status on squad detail page

Add a new GET /api/squads/{id}/members/status endpoint that returns each
member's derived working/idle/offline/unstable status, the issues each
agent is currently running, and the last observed activity timestamp.
The Squad detail page's Members tab consumes this snapshot to render a
status pill and an active-issue link next to each agent, with live
refresh wired through the existing task/agent/daemon WS events.

Human members are returned with status=null so the UI can keep them in
the same list without implying a presence signal. Archived agents stay
in the response and surface as offline rather than being filtered out.

Co-authored-by: multica-agent <github@multica.ai>

* fix(squads): address review feedback on member status endpoint

- i18n the "blocked" issue-status pill in squad members tab (was a
  bare literal that failed `i18next/no-literal-string` lint).
- Treat any dispatched/running task as working, even when its
  `agent_task_queue.issue_id` is NULL (chat / quick-create tasks).
  The agent slot is occupied regardless of whether we can render an
  issue link.
- Force `offline` for archived agents so they appear in the list
  but never look like they're still on duty, matching the RFC
  decision in MUL-2319.
- Include `workspaceKeys.squads` in the post-reconnect /
  workspace-switch bulk invalidation so members-status recovers
  after a disconnect during which task/runtime events were missed.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-18 10:35:18 +02:00
Anderson Shindy Oki
2d501322e9 fix: Squads page unable to scroll (#2764) 2026-05-18 15:19:16 +08:00
Naiyuan Qing
20c2f45b4a fix(views): surface backend error messages on mutation failures (MUL-2317) (#2772)
* fix(views): surface backend error messages on mutation failures (MUL-2317)

Mutation toasts across the views package were swallowing the backend
`error` string and showing only a generic i18n fallback. This made it
impossible for users to see why an operation failed (most visibly:
creating an issue with a duplicate title produced a vague "Failed to
create issue" toast).

The fix has three pieces:

1. Create-issue duplicate branch (A段)
   - New schema `DuplicateIssueErrorBodySchema` in core/api/schemas.ts.
   - `create-issue.tsx` parses `ApiError.body` via `parseWithFallback`
     and renders a dedicated amber-toned toast with a "view existing"
     link when the server returns `{ code: "active_duplicate_issue",
     issue: {...} }`. Schema drift downgrades to the normal error toast.
   - Schema intentionally omits `issue.status` so the toast does not
     depend on `StatusIcon`, which has no fallback for unknown enums.

2. User-facing mutation failure toasts (B段)
   - 47 sites converted to `err instanceof Error && err.message ?
     err.message : <existing fallback>` — preserves all existing
     code-specific branches (slug conflict, agent_unavailable,
     daemon_version_unsupported) and i18n keys.
   - Covers Type 1 (onError) and Type 2 (catch block) patterns across
     issues, projects, autopilots, inbox, runtimes, squads, comments,
     batch actions, workspace create, and agent config tabs.

3. Autopilot partial-success (Type 3)
   - New i18n keys `toast_create_partial_with_reason` /
     `toast_update_partial_with_reason` (double-brace `{{reason}}`).
   - `autopilot-dialog.tsx` captures `err.message` in the schedule
     `catch` and routes to the `_with_reason` variant when present,
     preserving the partial-success semantic (autopilot saved, schedule
     failed) while exposing the actual reason.

Explicitly out of scope:
- `packages/core/` mutation hooks (no global onError, no UI dependency)
- No `toastApiError` helper (matches existing 14+ correct sites)
- Sub-issue link aggregate `Promise.allSettled` keeps count-based toast
  (N independent requests cannot collapse to one err.message); only
  added a dev-side `console.error` per rejection.
- Clipboard catches and `useUpdateChatSession` (not API mutation toasts)

Tests:
- `packages/core/api/schemas.test.ts` — schema contract (valid body,
  forward-compat fields, rename rejection, missing issue, wrong types).
- `packages/views/modals/create-issue.test.tsx` — duplicate toast +
  view link, schema-drift fallback, err.message surfacing, non-Error
  fallback (4 new cases).
- `packages/views/autopilots/components/autopilot-dialog-i18n.test.ts`
  — real i18next, asserts rendered text contains the reason verbatim
  (guards against `{reason}` vs `{{reason}}` regression).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(autopilots): unify rotate-token catch + cover dialog partial-success render

Address reviewer feedback on PR #2772:

1. webhook-token rotate (`autopilot-detail-page.tsx`) now follows the
   `err.message ?? fallback` ternary used by the sibling trigger
   delete/add paths, instead of swallowing the error.

2. Extract `formatSchedulePartialFailureToast` so the dialog's
   partial-success branches and the i18n test exercise the same
   helper. The test now drives the actual format function, so a
   variable-name typo at the call site (e.g. `{ msg }` instead of
   `{ reason }`) fails the substring assertion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* test(modals): drop user.type for title in success path to dodge CI 5s timeout

The success-path test typed the 42-character title via userEvent which
triggers a controlled re-render per keystroke. On the slower CI runner
the whole test crept up to ~5s and intermittently tripped the default
vitest timeout. Setting the value in one shot via fireEvent.change cuts
the cost while leaving the submit + toast interactions on userEvent.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-18 13:44:10 +08:00
Naiyuan Qing
5ad1641b72 Revert "Squad archive dialog + role editor + transactional DeleteSquad (#2680)" (#2687)
This reverts commit 2980ead4c7.
2026-05-15 17:44:59 +08:00
Naiyuan Qing
2980ead4c7 Squad archive dialog + role editor + transactional DeleteSquad (#2680)
* docs(squad): address plan-review feedback for archive + role plan

Resolve the 4 items the reviewer raised on MUL-2265:

1. TS schema: declare `active_issue_count` as optional (`number | null | undefined`)
   so list/create/update Squad responses don't lie about their shape; only
   `getSquad` parses through SquadSchema.
2. Archive semantics: restrict TransferSquadAssignees to active issues
   (status NOT IN done, cancelled) so dialog count and SQL operate on one set
   and terminal-state issues keep their historical assignee.
3. Index assumption: corrected — `idx_issue_assignee (assignee_type,
   assignee_id)` exists and is sufficient at realistic squad cardinality;
   no new index needed.
4. Fixed `*int64` test comparison and added `.loose()` to SquadSchema per
   the local schemas.ts convention.

Co-authored-by: multica-agent <github@multica.ai>

* docs(squad): plan v3 — revert to count-all/transfer-all on archive

Reviewer round 2 surfaced two structural problems with plan v2's
active-only carve-out:

1. useActorName resolves squad names via ListSquads, which filters
   archived_at IS NULL. A closed issue with an archived-squad assignee
   would render as "Unknown Squad".

2. The status-only update path in UpdateIssue skips validateAssigneePair,
   so a done/cancelled issue with an archived-squad assignee could be
   reopened to in_progress, violating the "no active issue on an archived
   squad" invariant enforced elsewhere.

Both problems disappear by reverting to count-all + transfer-all: after
ArchiveSquad runs, no issue points at the archived squad, so neither
case can occur. The product trade-off is that closed historical issues
now show the leader agent instead of the archived squad in their
"Assigned to" badge — consistent with existing agent-level reassignment
behavior elsewhere in the product.

Field rename: active_issue_count -> issue_count.
TransferSquadAssignees SQL is unchanged (already transfers all).

Co-authored-by: multica-agent <github@multica.ai>

* docs(squad): add Task 2b — wrap DeleteSquad transfer + archive in one tx

Reviewer round-3 flagged that the v3 invariant ("after archive no
issue points to the squad") was asserted on the happy path only.
DeleteSquad's current best-effort impl breaks it two ways:
- transfer failure → slog.Warn but archive proceeds (Unknown Squad,
  reopen-into-archived-squad bugs reappear)
- archive failure after a committed transfer → 500 with squad still
  active but emptied

Task 2b rewrites DeleteSquad to run TransferSquadAssignees +
ArchiveSquad inside one pgx tx, mirroring the project.go:266-314
pattern. Publish moves below Commit. Adds two regression tests that
lock both partial-write failure modes.

Co-authored-by: multica-agent <github@multica.ai>

* feat(squad): replace native confirm() with AlertDialog and rewrite role editor as combobox

Backend:
- Add CountIssuesForSquad sqlc query (counts every issue assigned to a squad,
  no status filter — matches the existing transfer-all archive semantics).
- Extend SquadResponse with optional `issue_count` (`*int64` + omitempty,
  populated only by GetSquad to avoid an N+1 in the list endpoint).
- Wrap DeleteSquad's transfer + archive in a single pgx transaction so the
  v3 invariant ("after archive, no issue points to the squad") is durable
  rather than best-effort. Promote slog.Warn to slog.Error and check the
  parseUUIDOrBadRequest ok flag (silent zero-UUID was a #1661-class latent
  bug). Publish only after Commit so realtime never sees rolled-back state.
- Tests cover happy path (count, transfer-all including terminal statuses)
  and both rollback directions (transfer fail / archive fail) via a
  fault-injecting tx wrapper.

Frontend:
- Extend Squad TS type with `issue_count?: number | null` (optional —
  list/create/update legitimately omit it). Add SquadSchema with `.loose()`
  and wrap getSquad with parseWithFallback so older servers and count-error
  responses degrade to the dialog's "no count" copy variant.
- Replace `window.confirm()` with shadcn `ArchiveSquadConfirmDialog`
  (destructive variant, leader name + count + closed-issue caveat in the
  copy, Loader2 while pending). i18n keys added under squads.archive_dialog.
- Rewrite RoleEditor as a Popover + Command combobox: Pencil affordance is
  always visible, suggestions aggregate other members' roles, commit only
  on Enter or selecting a suggestion (blur discards), per-member savingId
  drives Loader2 so the spinner only renders on the row being saved.

Co-authored-by: multica-agent <github@multica.ai>

* fix(squad): discard RoleEditor draft on close and no-op blank Enter

Two reviewer findings on e0d754bf:

1. Closing the Popover (outside click, Esc, trigger re-click) left `query`
   in state, so reopening + Enter would commit the stale draft. Clear
   `query` on every non-saving close path.
2. With an existing role, opening the editor and pressing Enter on an
   empty input committed "" — `commit` only no-op'd when trimmed matched
   value. Treat blank Enter as a no-op; clearing a role would need an
   explicit clear action that doesn't exist yet.

Add two regression tests:
- close (via outside click) → reopen surfaces a clean input; Enter does
  not commit the stale draft
- blank Enter on an existing role does not call onSave

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(squad): add explicit Clear button to RoleEditor

Role is optional, but the previous fix turned blank Enter into a no-op
without exposing any other way to clear an existing role — that broke a
valid terminal state. Keep blank Enter as no-op; add a "Clear role"
button at the bottom of the popover that only renders when value is
non-empty and routes through onSave("").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 17:29:37 +08:00
Jiayuan Zhang
675ed02aa6 MUL-2216: persist Mine/All tab selection on Agents and Squads pages (#2624)
* MUL-2216: feat(agents,squads): persist Mine/All tab selection per workspace

Tab selection on the Agents and Squads list pages was held in
component-local state, so navigating into a detail page and back
remounted the list and reset the tab to the default "Mine". Move
`scope` into Zustand stores backed by `persist` +
`createWorkspaceAwareStorage`, matching the pattern used by the
Issues view store. Selection now survives list → detail → back
navigation and page reloads, scoped per workspace.

Only `scope` is persisted; `search`, `sort`, and other ephemeral
filters intentionally still reset on remount.

Co-authored-by: multica-agent <github@multica.ai>

* fix(views): reset scope to mine when switching to a workspace with no persisted value

zustand persist.rehydrate() is a no-op when storage returns null, so
workspaces with no entry kept the previous workspace's in-memory scope
("all" leaked from one workspace into the next). Provide a custom merge
that resets to the default "mine" when no persisted state is present.

Add coverage for the missing-storage workspace-switch case for both
Agents and Squads.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 14:11:22 +02:00
Naiyuan Qing
43b9a1173c refactor(agents): drop template chooser from create-agent dialog (#2615)
* refactor(agents): drop template chooser from create-agent dialog

Removes the blank-vs-template chooser, the template picker, and the
template detail step. The "Create agent" entry point now opens directly
on the form. The createAgentFromTemplate API and types remain
untouched — this only removes the UI entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* docs(squads): fix stale comment about createAgentFromTemplate

Squad-scoped create flow no longer goes through the template path;
the dialog now only calls api.createAgent then api.addSquadMember.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 18:05:37 +08:00
Naiyuan Qing
77b929fd3e feat(squads): add agent live peek hover card on member avatars (#2608)
* feat(squads): add agent live peek hover card on member avatars

Squad members tab now opens a live-state peek card on agent avatar
hover/focus — workload, current issue (clickable), and last activity.
Identity (description / runtime / skills / owner) stays on the existing
AgentProfileCard; new AgentLivePeekCard is the second `hoverCardVariant`
on ActorAvatar so the 23+ existing profile-card call sites keep their
behaviour. Reuses the workspace agent-task snapshot already fetched by
the presence dot, so this adds zero new requests per row. Failed
terminal tasks surface as a small ⚠ on the last-activity line without
polluting workload (workload stays current-state only, matching the
deliberate split documented in core/agents/types.ts).

Co-authored-by: multica-agent <github@multica.ai>

* fix(squads): only enable hover card for agent avatars

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 17:30:08 +08:00
Naiyuan Qing
5eb04f73e3 feat(squads): add tooltips and agent detail link to squad member row (#2603)
* feat(squads): add tooltips and agent detail link to squad member row

Replace native title attributes on the make-leader and remove buttons
with proper Tooltip components, and add a new icon button on agent
rows that navigates to the agent detail page. All three tooltips are
localised.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(squads): keyboard focus visibility + AppLink for agent detail

- Add group-focus-within:opacity-100 so Tab to the row's hover-only
  action buttons makes the container visible (previously opacity-0
  kept buttons focusable but invisible).
- Replace the agent-detail jump button's onClick+push() with AppLink
  href, restoring middle/Cmd+Click new-tab behavior. Removes the
  now-unused onViewAgent callback chain.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 16:35:15 +08:00
Naiyuan Qing
bc613c08b3 fix(squad): align squad detail tab width with agent detail (#2600)
Drop mx-auto + max-w-2xl wrappers around the Members and Instructions
tab content so the right pane fills the available width like the agent
detail page (TabContent uses flex h-full flex-col p-4 md:p-6).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 16:09:45 +08:00
LinYushen
d1c8c213e4 feat: extend pinyin search to all Agent/Member/Squad selectors (#2582)
Integrate matchesPinyin into:
- AssigneePicker (issue assignee selector)
- IssuesHeader (assignee filter bar)
- AgentPicker (autopilot agent selector)
- SquadDetailPage (add member/agent picker)
- QuickCreateIssue (agent/squad picker)
- CreateProject (lead picker)
- ProjectDetail (lead picker)
- ProjectsPage (lead filter)
- AgentsPage (agent search)
- SquadsPage (squad search)

Closes MUL-2179 extended scope.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:57:38 +08:00
Bohan Jiang
f15a745182 feat(squads): add Create Agent entry on Squad detail (MUL-2178) (#2579)
Adds a Create Agent button on the Squad detail Members tab, visible
only to workspace owner/admin (matching the AddSquadMember backend
gate). The dialog reuses the existing CreateAgentDialog — both the
manual and template paths now accept an optional squadId; when set,
the dialog runs addSquadMember after createAgent / createAgentFromTemplate
and skips the navigation to the agent detail page so the user lands
back on the Members tab.

Atomicity is best-effort frontend-serial (no new backend transaction):
on partial failure the dialog surfaces a warning toast and the agent
remains addable from the existing Add Member flow.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:32:28 +08:00
Bohan Jiang
87464f6c03 fix(squads): i18n the Squad pages to unblock views#lint (CI red on main) (#2542)
#2505 (Squad MVP) merged with 29 hardcoded English strings in JSX text
nodes — packages/views/squads/components/squads-page.tsx (4) and
squad-detail-page.tsx (25). The package's eslint config enforces
`i18next/no-literal-string` as ERROR for every .tsx file, so
@multica/views#lint has been red on main, which Turbo cascades to
@multica/web#build, @multica/desktop#build, and @multica/views#typecheck
— effectively blocking every open PR's frontend CI (#2538, #2540, etc.).

Rather than disabling the rule for the Squad files (which would just
hide debt in a high-visibility surface), wire up a proper i18n
namespace and replace every flagged literal.

Namespace plumbing
- New `packages/views/locales/en/squads.json` and
  `packages/views/locales/zh-Hans/squads.json` covering all 29 flagged
  strings, grouped by surface (page / inspector / name_editor /
  add_member_dialog / description_dialog / discard_changes_dialog /
  members_tab / instructions_tab).
- Registered in `packages/views/locales/index.ts` and
  `packages/views/i18n/resources-types.ts` so `t($ => $.squads.*)` is
  type-safe.

Component replacements
- `squads-page.tsx`: add `useT("squads")`, replace 4 literals.
- `squad-detail-page.tsx`: add `useT("squads")` to seven inner
  components that hold flagged text (`SquadDetailPage` / `InlineEdit
  Popover` / `AddMemberDialog` / `RoleEditor` / `SquadDescriptionEditor`
  / `SquadDescriptionEditorBody` / `SquadOverviewPane` / `SquadMembers
  Tab` / `SquadInstructionsTab` / `SquadDetailInspector`), replace all
  flagged literals.
- Plural members count uses i18next's standard `_one` / `_other`
  suffixes via `t(..., { count })` — matches the convention already
  used in `runtimes/usage` and `agents`.

Notes
- A few unflagged user-facing strings remain (tab labels in
  squadDetailTabs array, ternary alternatives like `"Save"` inside
  `{x ? <Loader/> : "Save"}`, the inline `confirm()` archive prompt,
  the `toast.success("Leader updated")` message). The eslint rule
  uses `mode: "jsx-text-only"` so it only flags string children of
  JSX nodes; attribute strings, object-literal values, and ternary
  alternatives slip past. Those are real i18n gaps too but expanding
  scope here would gold-plate the CI-unblock fix.

Verification
- `pnpm --filter @multica/views lint`: 0 errors (was 29). Remaining 13
  warnings are pre-existing in unrelated files and don't fail CI.
- `pnpm typecheck`: 6/6 packages pass — namespace types resolve, all
  selector calls infer correctly.
2026-05-13 19:35:31 +08:00
LinYushen
29082f7cfe feat: implement Squad feature MVP (#2505)
* feat: implement Squad feature MVP

- Add migration 084_squad: squad, squad_member, squad_activity_log tables
- Extend issue.assignee_type to support 'squad'
- Add sqlc queries for squad CRUD, member management, activity logs
- Add Go handler with full Squad API (CRUD, members, activity log)
- Register routes: /api/squads/*, /api/issues/{id}/squad-activity, /api/squad-activity
- Add Squad trigger logic:
  - Assign Squad immediately triggers leader
  - Every external comment on squad-assigned issue triggers leader
  - Anti-loop: squad members' comments don't trigger leader
  - Dedup: skip if leader already has pending task
- Add squad activity log API (方案 B) for leader no-op recording
- Add frontend TypeScript types (Squad, SquadMember, SquadActivityLog)
- Add protocol events: squad:created, squad:updated, squad:deleted

Co-authored-by: multica-agent <github@multica.ai>

* fix: address PR review blocking issues

1. validateAssigneePair now accepts 'squad' assignee_type
2. All squad endpoints validate workspace ownership via GetSquadInWorkspace
3. CreateSquadActivityLog restricted to squad leader agent only
4. AddSquadMember validates member exists in workspace
5. UpdateSquad auto-adds new leader to squad members
6. DeleteSquad transfers assigned issues to leader before deletion
7. IssueAssigneeType includes 'squad' in frontend types

Co-authored-by: multica-agent <github@multica.ai>

* feat: soft-delete squads via archive instead of hard delete

- Add migration 085: archived_at + archived_by columns on squad table
- ListSquads now excludes archived squads (ListAllSquads for admin)
- DeleteSquad → ArchiveSquad (sets archived_at, preserves all records)
- Transfer squad-assigned issues to leader before archiving
- SquadResponse includes archived_at/archived_by fields
- Frontend Squad type updated with nullable archived fields

Co-authored-by: multica-agent <github@multica.ai>

* feat: re-add Squads frontend entry (sidebar nav + pages)

Re-applies the frontend squad entry that was lost during a merge:
- Sidebar nav: Squads item with Users icon
- Paths: squads() and squadDetail() in workspace paths
- Routes: /squads and /squads/[id] pages
- Views: SquadsPage (list) and SquadDetailPage
- i18n: en 'Squads' / zh '小队'
- Reserved slug: 'squads'

Co-authored-by: multica-agent <github@multica.ai>

* fix: fix SquadsPage rendering - use PageHeader children pattern

PageHeader takes children, not title/actions props. The incorrect
usage caused a React rendering error. Now matches the pattern used
by autopilots and agents pages.

Co-authored-by: multica-agent <github@multica.ai>

* fix(squads): add API client methods and package export for squads pages

* feat: complete Squad frontend - create dialog, member management, API methods

- Add CreateSquadModal with name/description/leader selection
- Register 'create-squad' in modal registry
- Wire 'New Squad' button to open the modal
- Add full API client methods: createSquad, updateSquad, deleteSquad,
  addSquadMember, removeSquadMember
- Rewrite SquadDetailPage with:
  - Member list showing resolved names
  - Add/remove member UI
  - Archive squad button
  - Back navigation to squads list

Co-authored-by: multica-agent <github@multica.ai>

* feat: improve Squad UI - match create agent dialog style

- CreateSquadModal: proper Dialog with Header/Description/Footer,
  agent picker with avatars, textarea for description
- SquadDetailPage: centered max-w-2xl layout, ActorAvatar for members,
  Crown badge for leader, textarea for member description,
  improved spacing and visual hierarchy
- Renamed 'role' field label to 'Description' in add member form
  (describes the member's responsibilities in the squad)

Co-authored-by: multica-agent <github@multica.ai>

* feat(squad): add avatar, instructions; drop unique-name constraint

- 086: add squad.avatar_url
- 087: drop unique constraint on squad.name (squads with the same
  name are legitimate across teams; uniqueness was an accidental
  product constraint)
- 088: add squad.instructions (text, default '')
- UpdateSquad now COALESCEs avatar_url + instructions
- handler exposes Instructions in SquadResponse and accepts it in
  UpdateSquad

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): assignable + mention target; trigger leader on assign

- assignee picker and @mention suggestion list squads alongside
  agents and members; renders squad avatar/icon
- creating or updating an issue with assignee_type=squad enqueues
  a task for the squad's current leader (mirrors agent-assignee
  parking-lot rule: skip backlog only)
- workspace queries/hooks expose squads where needed for the
  pickers
- locales updated for new picker copy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): agent-style detail page with members + instructions tabs

- restructure squad detail page to mirror the agent detail page:
  320px inspector (creator, leader, created/updated) + tabbed
  pane (Members | Instructions) with dirty-guard AlertDialog
- inline name + avatar editing on the inspector
- inline description editor (modal textarea)
- members tab: leader + member picker with role descriptions,
  swap leader, edit member roles, remove
- instructions tab: ContentEditor + Save (mirrors agent pattern)
- squads list shows the squad avatar/icon
- core types + api.updateSquad accept avatar_url + instructions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): inject leader briefing on claim (protocol + roster + instructions)

When a squad's leader agent claims a task on a squad-assigned issue,
append a system-level briefing to the agent's Instructions composed of:

1. Squad Operating Protocol — hard-coded rules: leader is a
   coordinator, dispatch via @mention, stop after dispatching,
   resume on re-trigger, do not work outside the roster.
2. Squad Roster — leader self-row plus one row per non-archived
   member with a literal mention markdown string ([@Name](mention://
   agent|member/<UUID>)) the leader can paste verbatim. Round-trips
   through util.ParseMentions, enforced by a contract test.
3. Squad Instructions — the user-defined squad.instructions block,
   omitted entirely when empty so we do not leave a dangling heading.

Non-leader members claiming the same issue receive no briefing.

Tests cover: full squad with mixed agent/human members, lone leader,
archived agents skipped, empty user instructions, mention round-trip,
and the leader/non-leader claim-handler gate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(squad): tell leader not to restate issue context in dispatch comment

After observing leaders padding their delegation comments with full
re-summaries of the issue body and prior discussion, make the
Operating Protocol explicit:

- assignees on Multica already have the full issue (title,
  description, all comments, attachments) and workspace context;
- delegation comments should add only what cannot be inferred
  (who is picked, why, extra constraints), aim for two or three
  sentences;
- restating context is now an explicit hard rule violation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): unify leader evaluation into activity_log, add CLI command

- Squad member comments now trigger leader (only leader self-excluded)
- Replace squad_activity_log with activity_log (action: squad_leader_evaluated)
- Add CLI: multica squad activity <issue-id> <outcome> --reason
- Add API: POST /api/issues/{id}/squad-evaluated
- Update squad operating protocol to require evaluation recording
- Remove squad_activity_log table from schema and generated code

* feat(cli): add squad list, get, member list commands

* fix(squad): address review findings (P1+P2)

P1 fixes:
- Add 'squads' to reserved_slugs.json (source of truth)
- Add 'create-squad' to ModalType union
- Remove unused leaderOpen/selectedLeader in create-squad modal
- Replace literal JSX strings with i18n selectors (en + zh-Hans)

P2 fixes:
- Add 'squad' to mention regex (MentionRe)
- Fix human member lookup in squad briefing (use GetUser directly)
- Add squads routes to desktop app
- Add squad:created/updated/deleted to WSEventType + invalidation
- Reject archived squads as issue assignees

* fix(squad): restore zh-Hans key, publish activity event, invalidate issues on archive

- Restore create_project.title in zh-Hans modals.json (dropped by prior edit)
- Publish activity:created WS event after squad leader evaluation
- Invalidate issue queries on squad:deleted (archive transfers assignees)
- Add creator info to squad list cards

* fix(squad): realtime sync, rerun support, leader validation

- Use workspaceKeys.squads prefix for detail/member queries (realtime invalidation)
- Publish squad:updated after add/remove/role-change member mutations
- Support rerun for squad-assigned issues (targets leader agent)
- Reject assignment to squads whose leader is archived

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-13 18:46:20 +08:00