Files
multica/server/pkg/db/generated/squad.sql.go
Naiyuan Qing f29bd93444 feat(squads): rework Create Squad modal (MUL-2233) (#2645)
* feat(squad): accept avatar_url on CreateSquad

Threads avatar_url through the SQL query, sqlc-generated code, and the Go
handler so the create-squad flow can persist an avatar at creation time
instead of forcing a follow-up PATCH.

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

* feat(squad): add avatar_url to CreateSquadRequest

Extends the TS contract for the new backend field so the frontend can pass
an uploaded avatar URL through api.createSquad.

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

* feat(squads): rework Create Squad modal to match CreateAgentDialog (MUL-2233)

Replaces the cramped small-dialog flow with the same large-dialog shape used
by Create Agent: identity row (AvatarPicker + name + description with char
counter), grouped Leader picker (My Agents first, then Workspace Agents),
and a new multi-select Additional Members picker covering agents and
workspace members. The members trigger collapses to "+N" once more than
three are selected; promoting an agent to leader auto-drops it from the
additional-members list.

After createSquad, additional members are attached via Promise.allSettled
so a single failure surfaces a warning toast without blocking navigation —
the squad still exists and the user can retry from the Members tab.

Adds packages/views/modals/create-squad.test.tsx covering identity binding,
leader-group ordering, leader/member conflict sanitization, the empty- and
partial-failure success paths, and the create-failure recovery path.

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

* fix(squads): valid trigger HTML + drop conflicted leader from members

Two issues from PR #2645 review:

1. AdditionalMembersPicker's PopoverTrigger was a <button> containing
   MemberChip's remove <button>, which React/HTML flags as nested
   interactive content (hydration + a11y warning). Render the trigger as
   a <div role="combobox"> via Base UI's render prop so the chip's
   remove button is valid.

2. sanitizedMembers only hid the leader from rendered/submitted output,
   so promoting an additional member to leader then switching leader
   away resurrected the hidden pick. Drop it from selectedMembers at
   the moment of promotion via handleLeaderChange; sanitizedMembers is
   no longer needed.

Adds a test that promotes → switches leader and asserts the member is
not resubmitted.

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-15 13:11:08 +08:00

499 lines
13 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: squad.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const addSquadMember = `-- name: AddSquadMember :one
INSERT INTO squad_member (squad_id, member_type, member_id, role)
VALUES ($1, $2, $3, $4)
RETURNING id, squad_id, member_type, member_id, role, created_at
`
type AddSquadMemberParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
Role string `json:"role"`
}
func (q *Queries) AddSquadMember(ctx context.Context, arg AddSquadMemberParams) (SquadMember, error) {
row := q.db.QueryRow(ctx, addSquadMember,
arg.SquadID,
arg.MemberType,
arg.MemberID,
arg.Role,
)
var i SquadMember
err := row.Scan(
&i.ID,
&i.SquadID,
&i.MemberType,
&i.MemberID,
&i.Role,
&i.CreatedAt,
)
return i, err
}
const archiveSquad = `-- name: ArchiveSquad :one
UPDATE squad SET archived_at = now(), archived_by = $2, updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions
`
type ArchiveSquadParams struct {
ID pgtype.UUID `json:"id"`
ArchivedBy pgtype.UUID `json:"archived_by"`
}
func (q *Queries) ArchiveSquad(ctx context.Context, arg ArchiveSquadParams) (Squad, error) {
row := q.db.QueryRow(ctx, archiveSquad, arg.ID, arg.ArchivedBy)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const countSquadMembers = `-- name: CountSquadMembers :one
SELECT count(*) FROM squad_member WHERE squad_id = $1
`
func (q *Queries) CountSquadMembers(ctx context.Context, squadID pgtype.UUID) (int64, error) {
row := q.db.QueryRow(ctx, countSquadMembers, squadID)
var count int64
err := row.Scan(&count)
return count, err
}
const createSquad = `-- name: CreateSquad :one
INSERT INTO squad (workspace_id, name, description, leader_id, creator_id, avatar_url)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions
`
type CreateSquadParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
LeaderID pgtype.UUID `json:"leader_id"`
CreatorID pgtype.UUID `json:"creator_id"`
AvatarUrl pgtype.Text `json:"avatar_url"`
}
func (q *Queries) CreateSquad(ctx context.Context, arg CreateSquadParams) (Squad, error) {
row := q.db.QueryRow(ctx, createSquad,
arg.WorkspaceID,
arg.Name,
arg.Description,
arg.LeaderID,
arg.CreatorID,
arg.AvatarUrl,
)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const getSquad = `-- name: GetSquad :one
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE id = $1
`
func (q *Queries) GetSquad(ctx context.Context, id pgtype.UUID) (Squad, error) {
row := q.db.QueryRow(ctx, getSquad, id)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const getSquadByAssignee = `-- name: GetSquadByAssignee :one
SELECT s.id, s.workspace_id, s.name, s.description, s.leader_id, s.creator_id, s.created_at, s.updated_at, s.archived_at, s.archived_by, s.avatar_url, s.instructions FROM squad s WHERE s.id = $1 AND s.workspace_id = $2
`
type GetSquadByAssigneeParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
// Look up the squad when an issue is assigned to a squad.
func (q *Queries) GetSquadByAssignee(ctx context.Context, arg GetSquadByAssigneeParams) (Squad, error) {
row := q.db.QueryRow(ctx, getSquadByAssignee, arg.ID, arg.WorkspaceID)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const getSquadInWorkspace = `-- name: GetSquadInWorkspace :one
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE id = $1 AND workspace_id = $2
`
type GetSquadInWorkspaceParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
func (q *Queries) GetSquadInWorkspace(ctx context.Context, arg GetSquadInWorkspaceParams) (Squad, error) {
row := q.db.QueryRow(ctx, getSquadInWorkspace, arg.ID, arg.WorkspaceID)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const isSquadMember = `-- name: IsSquadMember :one
SELECT EXISTS(
SELECT 1 FROM squad_member
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
) AS is_member
`
type IsSquadMemberParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
}
func (q *Queries) IsSquadMember(ctx context.Context, arg IsSquadMemberParams) (bool, error) {
row := q.db.QueryRow(ctx, isSquadMember, arg.SquadID, arg.MemberType, arg.MemberID)
var is_member bool
err := row.Scan(&is_member)
return is_member, err
}
const listAllSquads = `-- name: ListAllSquads :many
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE workspace_id = $1 ORDER BY created_at ASC
`
func (q *Queries) ListAllSquads(ctx context.Context, workspaceID pgtype.UUID) ([]Squad, error) {
rows, err := q.db.Query(ctx, listAllSquads, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Squad{}
for rows.Next() {
var i Squad
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSquadMembers = `-- name: ListSquadMembers :many
SELECT id, squad_id, member_type, member_id, role, created_at FROM squad_member WHERE squad_id = $1 ORDER BY created_at ASC
`
func (q *Queries) ListSquadMembers(ctx context.Context, squadID pgtype.UUID) ([]SquadMember, error) {
rows, err := q.db.Query(ctx, listSquadMembers, squadID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SquadMember{}
for rows.Next() {
var i SquadMember
if err := rows.Scan(
&i.ID,
&i.SquadID,
&i.MemberType,
&i.MemberID,
&i.Role,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSquads = `-- name: ListSquads :many
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE workspace_id = $1 AND archived_at IS NULL ORDER BY created_at ASC
`
func (q *Queries) ListSquads(ctx context.Context, workspaceID pgtype.UUID) ([]Squad, error) {
rows, err := q.db.Query(ctx, listSquads, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Squad{}
for rows.Next() {
var i Squad
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSquadsByMember = `-- name: ListSquadsByMember :many
SELECT s.id, s.workspace_id, s.name, s.description, s.leader_id, s.creator_id, s.created_at, s.updated_at, s.archived_at, s.archived_by, s.avatar_url, s.instructions FROM squad s
JOIN squad_member sm ON sm.squad_id = s.id
WHERE s.workspace_id = $1 AND sm.member_type = $2 AND sm.member_id = $3
ORDER BY s.created_at ASC
`
type ListSquadsByMemberParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
}
// Find all squads a given entity belongs to in a workspace.
func (q *Queries) ListSquadsByMember(ctx context.Context, arg ListSquadsByMemberParams) ([]Squad, error) {
rows, err := q.db.Query(ctx, listSquadsByMember, arg.WorkspaceID, arg.MemberType, arg.MemberID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Squad{}
for rows.Next() {
var i Squad
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const removeSquadMember = `-- name: RemoveSquadMember :execrows
DELETE FROM squad_member
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
`
type RemoveSquadMemberParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
}
func (q *Queries) RemoveSquadMember(ctx context.Context, arg RemoveSquadMemberParams) (int64, error) {
result, err := q.db.Exec(ctx, removeSquadMember, arg.SquadID, arg.MemberType, arg.MemberID)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const transferSquadAssignees = `-- name: TransferSquadAssignees :exec
UPDATE issue SET assignee_type = 'agent', assignee_id = $2, updated_at = now()
WHERE assignee_type = 'squad' AND assignee_id = $1
`
type TransferSquadAssigneesParams struct {
AssigneeID pgtype.UUID `json:"assignee_id"`
AssigneeID_2 pgtype.UUID `json:"assignee_id_2"`
}
// Transfer all issues assigned to a squad to the squad's leader agent.
func (q *Queries) TransferSquadAssignees(ctx context.Context, arg TransferSquadAssigneesParams) error {
_, err := q.db.Exec(ctx, transferSquadAssignees, arg.AssigneeID, arg.AssigneeID_2)
return err
}
const updateSquad = `-- name: UpdateSquad :one
UPDATE squad SET
name = COALESCE($2, name),
description = COALESCE($3, description),
leader_id = COALESCE($4, leader_id),
avatar_url = COALESCE($5, avatar_url),
instructions = COALESCE($6, instructions),
updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions
`
type UpdateSquadParams struct {
ID pgtype.UUID `json:"id"`
Name pgtype.Text `json:"name"`
Description pgtype.Text `json:"description"`
LeaderID pgtype.UUID `json:"leader_id"`
AvatarUrl pgtype.Text `json:"avatar_url"`
Instructions pgtype.Text `json:"instructions"`
}
func (q *Queries) UpdateSquad(ctx context.Context, arg UpdateSquadParams) (Squad, error) {
row := q.db.QueryRow(ctx, updateSquad,
arg.ID,
arg.Name,
arg.Description,
arg.LeaderID,
arg.AvatarUrl,
arg.Instructions,
)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const updateSquadMemberRole = `-- name: UpdateSquadMemberRole :one
UPDATE squad_member SET role = $4
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
RETURNING id, squad_id, member_type, member_id, role, created_at
`
type UpdateSquadMemberRoleParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
Role string `json:"role"`
}
func (q *Queries) UpdateSquadMemberRole(ctx context.Context, arg UpdateSquadMemberRoleParams) (SquadMember, error) {
row := q.db.QueryRow(ctx, updateSquadMemberRole,
arg.SquadID,
arg.MemberType,
arg.MemberID,
arg.Role,
)
var i SquadMember
err := row.Scan(
&i.ID,
&i.SquadID,
&i.MemberType,
&i.MemberID,
&i.Role,
&i.CreatedAt,
)
return i, err
}