# Channel Create Settings Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Simplify Channel creation to name-only API/UI while moving Description and Channel visibility editing into Channel Settings.

**Architecture:** Backend Channel create becomes a narrow name-only mutation that always creates private Channels with empty descriptions. Backend Channel update carries optional name, description, and visibility patches through the app store and returns ChannelResponse with visibility. Web removes create-time description/channel visibility UI, sends name-only create requests, and renders a Settings visibility section backed by the new update field.

**Tech Stack:** Go API/store tests and handlers; React/TypeScript Web UI with TanStack Query; Node test runner via `npx tsx --test`; Go `testing` packages.

---

## File Structure

- Modify `internal/api/dto/channel.go`: shrink `ChannelCreateRequest`, add `Visibility` to `ChannelResponse`, add optional `Visibility` to `ChannelUpdateRequest`.
- Modify `internal/api/channel/api_channels.go`: call create store with name only; call update app store with visibility.
- Modify `internal/api/channel/channel_response_builders.go`: copy `ChannelSummary.Visibility` to response.
- Modify `internal/store/message/projections.go`: add `Visibility` to `ChannelSummary` and select `scope_channel_profiles.visibility` in channel lists.
- Modify `internal/store/channel/collaboration.go`: simplify `CreateChannelForUser` signature and fixed defaults; return visibility.
- Modify `internal/store/app/app_channels.go`: extend `UpdateChannelForUser` signature, validate/update visibility, preserve all-channel constraints, return visibility.
- Modify backend tests in `internal/api/channel/*_test.go` and `internal/store/app/*_test.go`.
- Modify `web/src/types/core.ts`: make `Channel.visibility` required.
- Modify `web/src/agentteam/api/workspace/core.ts`: make `createChannel` name-only and `updateChannel` support visibility.
- Modify `web/src/agentteam/workspace/conversation/components/CreateChannelModal.tsx`: remove description/channel visibility state and UI; keep member invite + agent visibility rows.
- Modify `web/src/agentteam/workspace/conversation/components/ChannelSettingsPane.tsx`: add visibility settings section.
- Modify i18n copy in `web/src/agentteam/i18n/locales/en/workspace/channel.json`.
- Modify frontend tests in `web/src/agentteam/api.test.ts`, `web/src/agentteam/workspace/conversation/components/CreateChannelModal.test.ts`, and source guard tests covering settings/create UI.

---

### Task 1: Backend create/update API contract

**Files:**
- Modify: `internal/api/dto/channel.go`
- Modify: `internal/api/channel/api_channels.go`
- Modify: `internal/api/channel/channel_response_builders.go`
- Modify: `internal/api/channel/channel_response_builders_test.go`
- Modify: `internal/api/channel/api_channel_crud_test.go`

- [ ] **Step 1: Update DTOs**

Change the channel DTOs to:

```go
type ChannelResponse struct {
    ID                string     `json:"id"`
    Name              string     `json:"name"`
    Slug              string     `json:"slug"`
    DisplayName       string     `json:"displayName"`
    Description       string     `json:"description"`
    Visibility        string     `json:"visibility"`
    Subsystem         string     `json:"subsystem,omitempty"`
    TeamID            string     `json:"teamId,omitempty"`
    TeamHandle        string     `json:"teamHandle,omitempty"`
    TeamDisplayName   string     `json:"teamDisplayName,omitempty"`
    Joined            bool       `json:"joined"`
    CanInvite         bool       `json:"canInvite"`
    CanManage         bool       `json:"canManage"`
    Archived          bool       `json:"archived"`
    SubscriptionState string     `json:"subscriptionState,omitempty"`
    PinnedAt          *time.Time `json:"pinnedAt,omitempty"`
}

type ChannelCreateRequest struct {
    Name string `json:"name"`
}

type ChannelUpdateRequest struct {
    Name        *string `json:"name"`
    Description *string `json:"description"`
    Visibility  *string `json:"visibility"`
}
```

- [ ] **Step 2: Update handler interfaces and calls**

Change `ChannelCRUDStore.CreateChannelForUser` to accept `(ctx, userID, workspaceID, name)` and change `ChannelCRUDAppStore.UpdateChannelForUser` to accept `(ctx, userID, workspaceID, channelID, name, description, visibility)`. In `Create`, call:

```go
channel, err := h.channels.CreateChannelForUser(
    r.Context(),
    authenticated.User.ID,
    workspaceID,
    strings.TrimSpace(request.Name),
)
```

In `Update`, call:

```go
channel, err := h.appStore.UpdateChannelForUser(
    r.Context(),
    authenticated.User.ID,
    workspaceID,
    channelID,
    request.Name,
    request.Description,
    request.Visibility,
)
```

- [ ] **Step 3: Return visibility from response builder**

Add `Visibility: summary.Visibility,` to `channelResponseFromSummary`.

- [ ] **Step 4: Update API tests**

In `internal/api/channel/channel_response_builders_test.go`, add `Visibility: "private"` to the `ChannelSummary` fixture and assert `require.Equal(t, "private", response.Visibility)`.

In `internal/api/channel/api_channel_crud_test.go`, update the fake store signatures and assertions so create requests are name-only and update requests can pass a visibility pointer.

- [ ] **Step 5: Run backend API package tests**

Run:

```bash
go test ./internal/api/channel
```

Expected: package passes.

- [ ] **Step 6: Commit backend API contract changes**

```bash
git add internal/api/dto/channel.go internal/api/channel/api_channels.go internal/api/channel/channel_response_builders.go internal/api/channel/channel_response_builders_test.go internal/api/channel/api_channel_crud_test.go
git commit -m "refactor(channel): simplify channel create api contract"
```

---

### Task 2: Backend store create defaults and visibility update

**Files:**
- Modify: `internal/store/message/projections.go`
- Modify: `internal/store/channel/collaboration.go`
- Modify: `internal/store/app/app_channels.go`
- Modify: `internal/store/app/app_channels_test.go` or relevant integration test file
- Modify: `internal/api/channel/api_channels_threads_integration_test.go` if create assertions need response visibility

- [ ] **Step 1: Add visibility to ChannelSummary projection**

Add `Visibility string` to `message.ChannelSummary`. In `ListChannelsForMember`, select `COALESCE(NULLIF(p.visibility, ''), 'private') AS visibility` immediately after description, scan it into `channel.Visibility`, and keep field order aligned.

- [ ] **Step 2: Simplify channel creation defaults**

Change `CreateChannelForUser` in `internal/store/channel/collaboration.go` to accept only name. Remove description and visibility parameters. Set:

```go
normalizedVisibility := "private"
```

Use `""` for inserted `description`, and return:

```go
Description: "",
Visibility:  normalizedVisibility,
```

- [ ] **Step 3: Add visibility validation for updates**

In `internal/store/app/app_channels.go`, add a helper near `UpdateChannelForUser`:

```go
func normalizeChannelProfileVisibility(raw string) (string, error) {
    visibility := strings.ToLower(strings.TrimSpace(raw))
    switch visibility {
    case "private", "public":
        return visibility, nil
    default:
        return "", badRequest("visibility must be public or private")
    }
}
```

- [ ] **Step 4: Update channel profile update mutation**

Change `UpdateChannelForUser` signature to include `visibility *string`. Set:

```go
nextVisibility := channel.Visibility
if nextVisibility == "" {
    nextVisibility = "private"
}
if visibility != nil {
    normalizedVisibility, err := normalizeChannelProfileVisibility(*visibility)
    if err != nil {
        return message.ChannelSummary{}, err
    }
    if channel.Slug == "all" && normalizedVisibility != channel.Visibility {
        return message.ChannelSummary{}, badRequest("The #all channel visibility cannot be changed")
    }
    nextVisibility = normalizedVisibility
}
```

Update SQL to set `visibility = $5` and return `Visibility: nextVisibility` in `ChannelSummary`.

- [ ] **Step 5: Update backend store tests**

Add or update tests to cover:

```go
updated, err := store.UpdateChannelForUser(ctx, userID, workspaceID, channelID, nil, nil, ptr("public"))
require.NoError(t, err)
require.Equal(t, "public", updated.Visibility)
```

Also assert invalid visibility returns an error and `#all` visibility updates are rejected.

- [ ] **Step 6: Run backend store tests**

Run:

```bash
go test ./internal/store/channel ./internal/store/app ./internal/api/channel
```

Expected: packages pass.

- [ ] **Step 7: Commit backend store changes**

```bash
git add internal/store/message/projections.go internal/store/channel/collaboration.go internal/store/app/app_channels.go internal/store/app/*test.go internal/api/channel/*test.go
git commit -m "feat(channel): update channel visibility in settings"
```

---

### Task 3: Web create modal and API helpers

**Files:**
- Modify: `web/src/types/core.ts`
- Modify: `web/src/agentteam/api/workspace/core.ts`
- Modify: `web/src/agentteam/workspace/conversation/components/CreateChannelModal.tsx`
- Modify: `web/src/agentteam/workspace/conversation/components/CreateChannelModal.test.ts`
- Modify: `web/src/agentteam/api.test.ts`
- Modify: `web/src/agentteam/i18n/locales/en/workspace/channel.json`

- [ ] **Step 1: Update Web API helper and types**

Change `Channel` to include:

```ts
visibility: ChannelVisibility | string
```

Change `createChannel` to:

```ts
export function createChannel(workspaceId: string, name: string) {
  return requestJSON<Channel>(workspaceIMPath(workspaceId, "/channels"), {
    method: "POST",
    workspaceId,
    body: JSON.stringify({ name }),
  })
}
```

Change `updateChannel` input to:

```ts
input: { name?: string; description?: string; visibility?: ChannelVisibility }
```

- [ ] **Step 2: Remove create-time description and Channel visibility state**

In `CreateChannelModal.tsx`, remove imports and state for `ChannelVisibility`, `ChoiceTile`, `FieldLegend`, `Textarea`, `cn`, `description`, and channel-level `visibility`. Keep `AgentChannelVisibility` for agent member rows.

- [ ] **Step 3: Simplify mutation variables**

Change `CreateChannelMutationVariables` and `CreateChannelMutationSnapshotInput` to omit `description` and Channel `visibility`. Change `buildCreateChannelMutationVariables` to return only name and selected members.

- [ ] **Step 4: Send name-only create request**

Change mutation function to:

```ts
return createChannel(workspaceId, variables.name)
```

- [ ] **Step 5: Remove create modal UI sections**

Delete the `FormField` for `create-channel-description` and the `fieldset` for `create_channel_modal.visibility_label`. Keep member picker UI unchanged.

- [ ] **Step 6: Update tests**

In `CreateChannelModal.test.ts`, remove description/visibility input arguments and expected output fields. In `web/src/agentteam/api.test.ts`, update the createChannel assertion to expect `JSON.stringify({ name: "..." })` and add/update updateChannel assertion for `{ visibility: "public" }`.

- [ ] **Step 7: Run frontend create/API tests**

Run:

```bash
cd web && npx tsx --test src/agentteam/workspace/conversation/components/CreateChannelModal.test.ts src/agentteam/api.test.ts
```

Expected: tests pass.

- [ ] **Step 8: Commit frontend create changes**

```bash
git add web/src/types/core.ts web/src/agentteam/api/workspace/core.ts web/src/agentteam/workspace/conversation/components/CreateChannelModal.tsx web/src/agentteam/workspace/conversation/components/CreateChannelModal.test.ts web/src/agentteam/api.test.ts web/src/agentteam/i18n/locales/en/workspace/channel.json
git commit -m "refactor(web): simplify channel creation modal"
```

---

### Task 4: Channel Settings visibility section

**Files:**
- Modify: `web/src/agentteam/workspace/conversation/components/ChannelSettingsPane.tsx`
- Modify: `web/src/agentteam/i18n/locales/en/workspace/channel.json`
- Modify: `web/src/agentteam/workspace/conversation/components/SurfaceNotificationPanel.test.ts` or `ChannelHeader.test.ts` source guard

- [ ] **Step 1: Import UI helpers**

In `ChannelSettingsPane.tsx`, import `ChoiceTile` and `ChannelVisibility`, and ensure `cn` is imported from `@/lib/utils`.

- [ ] **Step 2: Extend save patch type**

Change `saveChannelPatch` input type to:

```ts
patch: { description?: string; name?: string; visibility?: ChannelVisibility }
```

After successful update, also invalidate:

```ts
void queryClient.invalidateQueries({
  queryKey: workspaceQueryKeys.discoverChannels(workspaceId),
})
```

- [ ] **Step 3: Add derived visibility state**

Add:

```ts
const channelVisibility: ChannelVisibility =
  channel.visibility === "public" ? "public" : "private"
const canManageVisibility = channel.canManage && !allChannel
```

- [ ] **Step 4: Render Visibility & discovery section**

Add a `SettingsSection` after Basic info:

```tsx
{!allChannel ? (
  <SettingsSection
    description={t("channel.settings_visibility_help")}
    id="channel-visibility"
    title={t("channel.settings_visibility")}
  >
    <div className="grid gap-2 sm:grid-cols-2">
      {(["private", "public"] as ChannelVisibility[]).map((nextVisibility) => (
        <ChoiceTile
          aria-pressed={channelVisibility === nextVisibility}
          disabled={!canManageVisibility}
          key={nextVisibility}
          onClick={() => {
            if (!canManageVisibility || nextVisibility === channelVisibility) return
            void saveChannelPatch({ visibility: nextVisibility })
          }}
          selected={channelVisibility === nextVisibility}
        >
          <span className="min-w-0">
            <span className="block text-sm font-medium text-foreground">
              {t(`channel.visibility_${nextVisibility}`)}
            </span>
            <span className="mt-1 block text-xs leading-5 text-muted-foreground">
              {t(`channel.visibility_${nextVisibility}_hint`)}
            </span>
          </span>
        </ChoiceTile>
      ))}
    </div>
  </SettingsSection>
) : null}
```

Use existing project styling conventions if `ChoiceTile` requires selected-state classes.

- [ ] **Step 5: Add settings copy**

Add keys:

```json
"settings_visibility": "Visibility & discovery",
"settings_visibility_help": "Control who can discover and join this channel.",
"visibility_private": "Private",
"visibility_private_hint": "Only invited members can see and access this channel.",
"visibility_public": "Public",
"visibility_public_hint": "Workspace members can discover and join this channel from search."
```

Reuse existing create-modal wording if keys already exist under `create_channel_modal` by moving or duplicating into `channel` scope.

- [ ] **Step 6: Update source guard tests**

Add assertions that `ChannelSettingsPane.tsx` contains `settings_visibility` and `saveChannelPatch({ visibility:`. Add assertions that `CreateChannelModal.tsx` no longer contains `create-channel-description` or `create_channel_modal.visibility_label`.

- [ ] **Step 7: Run frontend settings tests**

Run:

```bash
cd web && npx tsx --test src/agentteam/workspace/conversation/components/SurfaceNotificationPanel.test.ts src/agentteam/workspace/conversation/components/CreateChannelModal.test.ts
```

Expected: tests pass.

- [ ] **Step 8: Commit settings UI changes**

```bash
git add web/src/agentteam/workspace/conversation/components/ChannelSettingsPane.tsx web/src/agentteam/i18n/locales/en/workspace/channel.json web/src/agentteam/workspace/conversation/components/SurfaceNotificationPanel.test.ts web/src/agentteam/workspace/conversation/components/CreateChannelModal.test.ts
git commit -m "feat(web): manage channel visibility in settings"
```

---

### Task 5: Final verification, review, PR, merge gate

**Files:**
- No planned source edits unless verification or review finds issues.

- [ ] **Step 1: Run full targeted verification**

Run:

```bash
go test ./internal/api/channel ./internal/store/channel ./internal/store/app
cd web && npx tsx --test src/agentteam/workspace/conversation/components/CreateChannelModal.test.ts src/agentteam/api.test.ts src/agentteam/workspace/conversation/components/SurfaceNotificationPanel.test.ts
cd web && npm run typecheck
```

Expected: all pass.

- [ ] **Step 2: Set review context**

Set AgentTeam review context to the worktree root:

```text
/Users/lonng/devel/pingcap/agentteam/.worktrees/channel-create-settings-20260709
```

- [ ] **Step 3: Request Reviewer 3 review**

Ask Reviewer 3 to review the branch with focus on:

- Create API has only necessary fields.
- Channel visibility default/private semantics are correct.
- Settings visibility update invalidates caches and cannot modify `#all`.
- Tests cover API, store, and Web behavior.

- [ ] **Step 4: Fix review blockers and re-run verification**

If Reviewer 3 reports blockers, patch them, commit fixes, and re-run the commands from Step 1.

- [ ] **Step 5: Create PR and watch it**

Push branch, create PR with summary and verification, then create a Gitea watch for the PR in this thread.

- [ ] **Step 6: Send merge only after review is clean**

Only after Reviewer 3 approves and verification remains green, send the Merge instruction on the PR.
