Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,53 @@ Channel details now provides an Edit action at
Edit and Remove sit at the top right beside the breadcrumb, with a 12px gap.
On narrow screens, the action group wraps below the breadcrumb and stays right aligned.
The editor follows the approved [Figma frame](https://www.figma.com/design/FaVJx5IZeQX9jHcUi55ndB/?node-id=63-327)
and the existing Telegram connection form.
and the existing Telegram connection form, with the September 16 request
to limit editing to Label and Skill name.

## Shared Components

- `ChannelSkillField` is shared by connection and editing, including its label,
optional state, input, and field error presentation.
- Both pages use `ChannelServicePicker`, `useChannelServiceChoices`, the existing
connection styles, shell, loading skeleton, and toast provider.
- The form contains only Skill name and Services, with no Advanced settings
section or inputs for Skill version and Bot instructions.
- Channel names and bot tokens are not editable through this API. Tool sets,
extra tool names, instructions, skill version, and credential source are
retained from the loaded config.
- Both pages reuse the existing connection styles, centered content, shell,
loading skeleton, and toast provider.
- The edit form contains only Label and Skill name. It does not load the
UserService inventory or display a Services selector. Creation retains its
existing service selection; details retain their read-only authorization list.
- Bot tokens are not editable. Tool sets, extra tool names, instructions,
service selectors, skill version, and credential source are retained from the
loaded config (clearing Skill name also clears its version).

## API Behavior

The API contract was checked against `origin/feature/integrate` at
`6c929a00db3d7636c91bc719478043a5fd331f7c`, specifically
`ChannelCallbackEndpoints.cs` and `ChannelRegistrationServiceSelection.cs`.
The runtime-config contract was checked against `origin/feature/integrate` at
`6c929a00db3d7636c91bc719478043a5fd331f7c`, specifically `ChannelCallbackEndpoints.cs`. Label updates use NyxID's existing
`backend/src/handlers/channel_bots.rs` contract; no backend changes are required.

- GET and POST use `/api/channels/registrations/{registrationId}/runtime-config`.
Detail identity and scope must match the route.
- Opening the editor requires a fresh successful GET, including when Query has
cached data from an earlier visit. Subsequent readback never resets user input.
- POST sends the complete known runtime config because omitted fields are not
patches. Strings edited by the user are trimmed as the backend parser expects.
- Clearing Skill name also clears its version. Removing service authorization
removes selectors for the deselected service, preserving other selectors.
- Services use the same authenticated inventory and bearer grant filtering as
creation. Saved services absent from current choices remain visible as
unavailable until deliberately removed. They are never silently dropped.
- Existing `nyxid_default` registrations retain their mode until the user
explicitly switches to individual selection. Zero individually selected
services is sent as an explicit empty allowlist.
- Opening the editor requires fresh successful config, registration-list, and
personal NyxID bot-list reads, even when Query has cached data. The exact owned
registration's `nyx_channel_bot_id` and matching platform identify the bot.
Missing/cross-scope identities block the editor; registration IDs never serve
as bot IDs. Later readback never resets user input.
- Label uses the actual NyxID bot label, not the runtime-config `label` placeholder
(currently the registration ID). Only a changed label triggers
`PATCH /api/v1/channel-bots/{botId}` with `{ "label": "..." }`. NyxID requires a
non-empty trimmed label of at most 128 UTF-8 bytes. The PATCH response must
confirm the exact bot ID, platform, and label before the safe query cache is
updated; credential fields and raw diagnostic bodies are discarded.
- A changed Skill name submits the complete known runtime config because omitted
runtime fields are not patches. Only Skill name is trimmed; other fields are
retained. The request omits `authorization_mode` and `service_ids`, allowing
the backend to preserve its current service selection, including legacy
defaults and explicit empty allowlists. Backend authorization checks still
apply; there is no frontend authorization override.
- When both fields change, Label saves first, then Skill name. A rejected Label
request leaves both inputs intact and skips the runtime update. If Label has
saved but Skill fails, the editor explicitly reports partial success and a
retry submits only the still-unsaved changes. The two APIs are not atomic.
- A Label-only save returns to details after the confirmed PATCH without
submitting runtime config or performing an unnecessary config readback.
- After an acknowledged POST, the form makes one GET for accurate feedback and
returns to channel details. The Save action remains busy across both requests
and rejects duplicate submission. There is no separate confirmation step,
Expand Down Expand Up @@ -75,18 +88,17 @@ The API contract was checked against `origin/feature/integrate` at

Focused integration cases cover exact identity, safe decoding, unsupported
config, detail-to-edit navigation, whole-config preservation, accepted readback,
explicit clearing, unavailable services, field errors, cached detail revalidation,
explicit clearing, label validation and exact bot identity, partial-save retry,
field errors, cached detail revalidation,
legacy authorization, delayed/failed readback navigation, duplicate-submit
protection, failed-save retry, unsaved navigation, and unmounts during POST/GET.
Existing creation, channel listing/details, API, navigation, route configuration,
and locale tests protect the reused surfaces.

The original editor was verified against the configured remote backend for
configuration prefill, service selection, and desktop and 390px layouts.
The save-flow revision uses the focused integration cases above; the local
preview compiles on port 5173, its API proxy responds, and the browser shows the
login page. Its OAuth callback uses the same origin. No live bot configuration
was changed during verification.
configuration prefill and desktop and 390px layouts. The names-only revision is
covered by API-boundary integration tests. No live bot configuration is changed
for verification.

Local verification is restricted to affected Jest files, changed-file Biome,
the test stability guard, baseline integrity, and diff checks. Full frontend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ Channels is a first-level destination in the Workflow Activity vNext sidebar.
It lets the signed-in owner view their connected bots and inspect a connection.

The three pages follow the latest simplified [Figma design](https://www.figma.com/design/FaVJx5IZeQX9jHcUi55ndB?node-id=7-226)
(list frame `7:2`, detail frame `6:131`, Telegram form `7:226`). The current design intentionally keeps
channel details read-only with Remove as the sole resource action. The earlier
runtime-configuration editor is outside this iteration of issue #3617.
(list frame `7:2`, detail frame `6:131`, Telegram form `7:226`). The initial detail
design used Remove as its sole resource action. The current detail page also
links to the editor documented in
[Channel Runtime Editor](../2026-09-15-channel-runtime-editor.md); its edit form
now exposes only Label and Skill name, preserving existing service authorization.

Channel refresh is explicitly user driven. Do not add background polling or
refresh on focus/reconnection to these pages.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
export default {
'channels.edit.label': 'Label',
'channels.edit.labelError':
'Enter a non-empty label. If it is too long, shorten it and try again.',
'channels.edit.labelFailed':
'Could not save the label. Check it and try again.',
'channels.edit.partialSave':
'Label saved, but the skill name could not be updated. Try saving again.',
'channels.edit': 'Edit',
'channels.edit.title': 'Edit {platform}',
'channels.edit.loadingTitle': 'Edit channel',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export default {
'channels.edit.label': 'Label',
'channels.edit.labelError': 'Label 不能为空;如果名称过长,请缩短后重试。',
'channels.edit.labelFailed': '无法保存 Label,请检查后重试。',
'channels.edit.partialSave':
'Label 已保存,但 Skill 名称更新失败,请再次保存。',
'channels.edit': '编辑',
'channels.edit.title': '编辑 {platform}',
'channels.edit.loadingTitle': '编辑渠道',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {
ExportOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { Button, Modal, Tooltip } from 'antd';
import { Button, Modal } from 'antd';
import * as React from 'react';
import { channelsApi } from '@/shared/api/channelsApi';
import { t } from '@/shared/i18n/messages';
import { history } from '@/shared/navigation/history';
import { AevatarContentSkeleton } from '@/shared/ui/AevatarContentSkeleton';
import AevatarTooltip from '@/shared/ui/AevatarTooltip';
import { useConsoleToast } from '@/shared/ui/ConsoleToast';
import {
buildChannelEditHref,
Expand Down Expand Up @@ -206,7 +207,7 @@ export default function ChannelDetailsPage({
? t('channels.name.loading', 'Loading name…')
: t('channels.name.unavailable', 'Name unavailable'))}
{registration.botId && !channelName && !bots.isPending ? (
<Tooltip
<AevatarTooltip
title={t('channels.name.retry', 'Reload channel name')}
>
<Button
Expand All @@ -219,7 +220,7 @@ export default function ChannelDetailsPage({
loading={bots.isFetching}
onClick={() => void bots.refetch()}
/>
</Tooltip>
</AevatarTooltip>
) : null}
</h1>
<p className="channels__identifier">
Expand Down
Loading
Loading