First sync down from the trunk under the agreed flow: common syncs into ui
whenever the trunk moves, and ui delivers back at milestones — always after
a sync, so the delivery merge carries no conflicts of its own.
Only two files conflicted, both trivial, which confirms the two branches
resolved the shared 2.6-vs-mainline hunks the same way:
- PlusMenu.tsx: kept the UI branch's semantic color tokens over the trunk's
hardcoded hex; the tokens travel back to common on the next delivery.
- AiMessageBubble.tsx: identical code, comment wrapping only.
Both apps lint clean; platform typecheck clean; i18n parity clean; the six
remaining client typecheck errors are byte-identical to main.
Carries the 2.6 work that landed after the fix2 release onto the 2.7
trunk. feat/2.7-common now equals origin/main (2.6.0 + hotfix1 + hotfix2)
plus everything feat/2.6.0 added since.
The nine conflicts are the same "mainline fixed it / the 2.6 line changed
it" files resolved in the feat/2.7-ui merge, and they are resolved
identically here so the two branches stay reconcilable:
- chat_service.py: mainline's interrupted-stream persistence plus the 2.6
line's classified error payload.
- redis_callback.py: mainline — the 2.6 line still referenced the outer
`node_id` inside the closure instead of its `one_node_id` parameter.
- chatApi.ts: all three error fields (errorText, errorType, errorDetail).
- useAiChat.ts / useAiChatSSE.ts: the 2.6 line's 3-arg onError and
classification payload, with mainline's localized connection-lost copy.
- AiMessageBubble.tsx: mainline's rule that a partially streamed answer
still renders, with the 2.6 line's rich ChatErrorCard below it.
- PlusMenu.tsx / Roles.tsx / linsight.py: the 2.6 line's icon, its
extracted roleMenuSelection module, and mainline's comment wording.
Housekeeping matches the UI merge: froze mainline's lint violations, added
the missing 11059 error copy in zh-Hans/en/ja, guarded one optional-typed
artifact preview callback.
Both apps lint clean; platform typecheck clean; i18n parity clean. The six
remaining client typecheck errors are byte-identical to main.
Rebases the UI line onto the released mainline: feat/2.7-ui starts from
origin/main (2.6.0 + hotfix1 + hotfix2) and takes in all the UI work that
accumulated on feat/2.6.0-ui — which had already merged feat/2.6.0, so the
2.6 feature work rides along in the same merge.
Both sides forked before hotfix2, so the twelve conflicts are the same
"mainline fixed it / the UI line rewrote it" files. Resolution rule:
mainline wins on logic, the UI line wins on presentation and design tokens.
- chat_service.py: mainline's interrupted-stream persistence plus the UI
line's classified error payload.
- redis_callback.py: mainline — the UI line still referenced the outer
`node_id` inside the closure instead of its `one_node_id` parameter.
- chatApi.ts: all three error fields (errorText, errorType, errorDetail).
- useAiChat.ts / useAiChatSSE.ts: the UI line's 3-arg onError and
classification payload, with mainline's localized connection-lost copy.
- AiMessageBubble.tsx: mainline's rule that a partially streamed answer
still renders, with the UI line's rich ChatErrorCard below it.
- ContextChips.tsx / SaveAsButton.tsx: mainline — it unified the
uploading/uploaded chip lists and added download-without-preview.
- PlusMenu.tsx / WorkspacePanel.tsx: the UI line's semantic color tokens,
kept together with mainline's icon choice and its no-flex-1 layout fix.
- Roles.tsx: the UI line's extracted roleMenuSelection module (constants
verified identical to mainline's inline copy).
Housekeeping: froze mainline's lint violations (its branch carries no
suppressions file), added the missing 11059 error copy in zh-Hans/en/ja,
and guarded one optional-typed artifact preview callback.
Both apps lint clean; platform typecheck clean; i18n parity clean. The six
remaining client typecheck errors are byte-identical to main and predate
this merge.
Two more baseline components, plus the tokens they forced into the open.
Modal is one shell for every business dialog: 56px header, a body that
is the only scrolling area, a fixed footer. The spec's pinned values —
four sizes with their step-down ladder, the 40% black mask with no blur,
the z-tier, the 200/160ms curves, the three exits — live in the
component, so a business page never restates them. Radix Dialog is the
base, which is what adds @radix-ui/react-dialog to the catalog.
Input covers every single-line field (plus password, search and the
multi-line Textarea) behind one shell: sizes, the four states and the
touch rules baked in, every string a prop.
Two token groups come with them:
- `shadow-focus`, the control focus ring. Deliberately not a third
elevation tier — it indicates focus and is built from existing values,
the exception 圆角与阴影规范 §4 allows. Its colour sits in its own var
so an error / warning field swaps the tint without a second token.
- Four `z-*` overlay tiers (modal / popover / toast / tooltip), ordered
so each can cover the one below. Hand-rolled z-[…] values are what
produced the old z-50 / z-[100] / z-[110] / z-[9999] zoo; 组件-Modal
弹窗 §5 is now the SSOT and every new overlay picks a tier.
Client's style.css and tailwind.config keep their value-for-value copy
of the preset, per the token SSOT rule. Specs and demo pages updated;
the docs sidebar gains 输入框 Input.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings the main line's 14 commits onto the design-system branch. Where
the two disagreed, the rule was: take the incoming behaviour, keep this
branch's semantic tokens.
- The knowledge space header and its file list: incoming wins outright,
since the whole point is to land the list-view rework here. FileTable
is deleted, FileListView/FileListRow/FileListToolbar arrive, and the
hex those ported files carried is converted to the semantic tokens
this branch runs on (#1d2129/#4e5969/#86909c/#e5e6eb/#f7f8fa →
text-1/text-2/text-3/border-base/fill-1). #8a94a6, #ebebeb and
#ECECEC stay: no semantic token maps to them.
- DropdownMenuCheckboxItem keeps this branch's tokens while taking the
incoming indicatorSide behaviour.
- ChannelPreviewDrawer keeps its tokens and takes the incoming removal
of the `rejected` branch (a rejected application must be resubmittable).
- The two square cards take incoming whole — the missing-icon and
broken-favicon fixes are behaviour, and their new #A9AEB8 has no token.
- useFileUpload and useWebsocket take incoming: those are the main
line's lint cleanup, already the newer form.
Suppressions pruned after the merge; client and @bisheng/ui typecheck
clean, client lint reports nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Shell per spec settled values: rounded-2xl, gray overlay (bg-gray-500/90),
border #ebebeb + soft shadow, p-5 edge / gap-4 block rhythm, title
text-base font-medium leading-6 with semantic text-text-1.
Footer buttons move to the Button dual-axis API (default/outlined cancel,
primary/solid confirm, built-in loading) in both the main picker and the
duplicate-files dialog. Tree density: container padding 12->4, uniform
4px row spacing via space-y-1. Mobile fullscreen: shrink the fixed-footer
reservation 84->64px to remove the dead gap above the action bar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the file-list rework from the cofco branch. FileTable's column
layout goes away; the space detail renders FileListRow inside
FileListView, and everything the table used to own in its headers —
sorting — plus the search / filter / view-toggle that lived in the space
header now share one 44px FileListToolbar, identical in list and card
mode. The header keeps identity, batch actions and 新增.
Along for the ride, all from the same rework:
- Toolbar chrome: the grey band paints only in list mode (it reads as
the list's header row); the view toggle names the view you are in, not
the one clicking would take you to; the status filter can key off
"待审核" via a UI-only sentinel, since pending uploads have no
file_status the backend could filter on.
- Rows: the meta line (date | tags | encoding) drops to 10px so its
three parts share one 18px line, with a matching TagGroup text-list
variant; the `#` prefix rule flips to "every variant except pill" so a
new text variant cannot silently lose it.
- Horizontal padding moves from the page container down to the header,
toolbar and card grid, so an H5 row's selected background spans the
full width instead of stopping 16px short.
- FileIcon holds icon components rather than pre-sized elements, so a
caller passing a smaller slot gets a scaled glyph instead of a cropped
one — only the folder branch honoured that before.
- Selection checkbox styling moves into selectionCheckboxStyles, and
RoundCheckbox gains `disabled` so unselectable rows keep the column's
alignment.
AddToKnowledgeModal's footer buttons switch to the Button dual-axis API
(color × variant × size) and its `loading` prop, dropping the
hand-rolled heights, paddings and Loader2 spinner the design system
already provides.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On this branch the two menus still live in the space header (the list
view that moves them into a toolbar is cofco-only), but they were the
last ones built from raw DropdownMenu primitives — their own surface,
their own row geometry. Both switch to ActionMenuContent /
ActionMenuItem, so they match every other menu, and their eleven
hand-written rows collapse into two option tables.
Same two shared additions as cofco's deda10b27:
- DropdownMenuCheckboxItem gains `indicatorSide`. Its check has always
been a left gutter, indenting every label by 32px whether or not
anything is checked; "right" keeps labels flush and puts the check in a
trailing slot. Default stays "left", so existing menus are untouched.
- ActionMenu exports ActionMenuCheckboxItem and
actionMenuSectionLabelClassName for the group headings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"已驳回" was modelled as a fourth status across the square card, the
discovery home and the preview drawer, and it rendered as a disabled
button — so a user whose application was turned down had no way to ask
again, on any of the three surfaces. Rejection is not a state the UI
needs: the backend's `rejected` now collapses into "join", which is what
a resubmittable application looks like.
The square card also stops standing in a /default-avatar.png when a
channel has no source icons — freshly created channels legitimately have
none — and a source whose favicon fails to load hides that one image
instead of leaving the browser's broken-image glyph. Icon overlap and the
meta line's grey follow the design.
Suppressions shrink with the removed branches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four params/body objects these endpoints assemble are written to and
handed straight to http — nothing reads a property back — so they carry
Record<string, unknown> instead of Record<string, any>. Suppression
budget shrinks with them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twenty-one of the fifty-three standing violations, fixed rather than
suppressed:
- useWebsocket: two switch cases get block scopes, so their `const`s
stop leaking into sibling cases (9 × no-case-declarations).
- FilePreview: both @ts-ignore comments were dead — __APP_ENV__ has had
a global declaration for a while — so they just go.
- useFileUpload: the `_raw` duplicate-row passthrough is now modelled on
KnowledgeFile as RawSpaceChild instead of being cast in and out at
three call sites; upload rejections narrow through one readUploadError
helper rather than `err: any` plus two `(err as any)` reads; and the
three `as any` on showToast were never needed — TShowToast has always
accepted `severity`.
Two things the removed `any`s had been hiding: i18next's t() rejects a
runtime-assembled `api_errors.{code}` key (it is not in the generated
key union — exists() is the real guard, now stated as such), and its
interpolation values must be scalars, so errorData is filtered to them.
Suppressions pruned accordingly; typecheck still clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The supported-formats hint was the only tooltip here still hand-skinned
— white panel, no arrow, its own text colour — so it read as a
different kind of popup from every other tooltip on the page. It drops
back to the shared TooltipContent defaults and moves to `side="top"`,
keeping only the width cap and z-index it actually needs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The top-offset cap (min((container − content) ÷ 2, 200px), built from
two flexible spacers) was a second positioning mechanism living beside
plain centering, and it only ever mattered for a handful of unusually
tall containers.
It goes: StateView now centers on both axes, full stop. The few areas
that do need the block higher — much taller than their content, or with
a floating dock over the bottom edge — pass an asymmetric bottom
padding through className (`pb-16`, `pb-[112px]`), and centering
resolves inside what's left, lifting the content by half that amount.
twMerge keeps the caller's padding over the component's own.
Docs follow: §6 rewritten around the one mechanism, the demo swaps its
360-vs-900px height comparison for a default-vs-nudged pair, and the
props table records className as the nudge channel. With this the
spec's four visual open questions are all closed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The State spec landed yesterday as prose; this is the component, plus
the revision the designer asked for on review: drop the two typography
sets. Scenario decides what you write and which artwork you pick, but
never how it is typeset — so there is one layout (16 / 4 / 16), and
whether 主提示 renders as a title or as description depends on a single
question: does a 辅助说明 follow it. A lone line is not a heading; 16/24
bold makes "还没有知识库" shout louder than the page around it.
StateView therefore has one axis, `size` (page / panel / inline) — the
container's size, per §3. The inline tier types `image`/`action` as
`never`: the spec says they're ignored there, and a compile error says
it better than a dev warning this package has no channel for. Exported
as StateView, not State, so it never reads as a React state variable.
First four consumers migrate off their hand-rolled shells:
PermissionEmptyState (which feeds five call sites), ChatEmptyState,
KnowledgeListPanel and SkillSelector.
Docs site: the demo page, and a preview-card exemption in the doc CSS —
the prose rules were flattening every <p> a live component renders to
14/22/400, so the State page's title tier looked identical to its
compact one. That page is exactly where a designer comes to eyeball the
hierarchy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On a toast whose copy wraps, the flex row centred the icon against the
whole text block — it floated somewhere beside the second line instead
of marking the start of the message. It now sits in a one-line-tall box
aligned to the top, so it rides the first line; the height comes from
--leading-body, which remaps per breakpoint on its own.
The close button also drops to text-4 and moves 16px off the copy,
matching the action button: it is an escape hatch, not something to
draw the eye.
Docs: the Toast spec records both, and the feedback demo page now
mounts a real Toaster so its "thanks for the feedback" confirmation
shows up in the demo the way it does in the app.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The eight status illustrations lived in client, so platform had no way
to reach them and their brand greens rode --brand-*, which renders the
darker UI green rather than the artwork's own.
They move to packages/ui/src/components/Illustration behind an
IllustrationProps type, and the palette becomes its own ramp: --illus-
100/300/500, blue mirroring the brand blue, green keeping the source
SVGs' vivid greens, plus a theme-independent .illus-grey draft mode
(grey collapses 300 onto 100 — pure white washed out on white, e.g. the
magnifier halo). Declared in design-token.cjs as the SSOT and carried by
tokens.css. No Tailwind classes: SVG presentation attributes ignore
var(), so components set them via inline style.
client keeps a re-export shim at ~/components/illustrations so call
sites are untouched. KnowledgeSpaceIcon stays behind: it reads the
Recoil theme, which the library contract forbids, and swaps whole
drawings rather than re-tinting one. The gallery gains the eighth
illustration (SystemError), which the inventory had been missing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two "新增授权" dialogs disagreed at the footer: one had a cancel
button, the other only submit; one used DialogFooter's own responsive
rules, the other a hand-rolled row. On mobile — where both become
full-screen sheets — that read as two different dialogs.
PERMISSION_FOOTER_ACTIONS_CLASS holds the shared rule: right-aligned at
natural width on desktop, two half-width buttons tiling one row under
768px (same breakpoint as the shell). The draft picker drops
DialogFooter for a plain row so the divider can sit directly above the
buttons on mobile, with the relation picker stacked above it. The
resource grant tab gains the missing cancel button via an optional
onCancel, and its submit switches to the Button loading prop instead of
appending "..." to its own label.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Root cause of the channel edit page's unresponsive inputs/switches:
useLocalize() returns a new function identity every render, which
cascaded through a useCallback dep chain into a "hydrate form from
server data" effect, causing it to re-fire (and reset the form) on
every keystroke.
The exit animation collapses a grid row to 0fr, which only takes effect
if the row clips its content — so the inner box carried overflow-hidden
permanently. That box hugs the toast exactly, and shadow-popup is drawn
entirely outside those bounds, so every toast rendered with its shadow
cut off.
Clip only while the row is collapsing; the toast has faded out by then,
so the shadow's absence is invisible and the collapse still animates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 2.6.0 merge brought back ~70 bare neutral hexes in code written while this
branch was folding them away — including permissionDialogStyles.ts, where the
classes from the deleted ChannelPermissionDialog landed.
Dark values used as fills (bg-[#1D2129], border-[#212121], the #999999 dot and
scrollbar-handle backgrounds) are left as they are: fill-1..4 are all light, so
there is no token that carries that intent, and mapping them would silently
turn a dark surface near-white.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Conflicts were all the same shape: this branch swapped hardcoded hexes for
semantic tokens while 2.6.0 changed the logic on the same lines. Resolved by
taking 2.6.0's version and replaying the token swaps onto it — verified
mechanically that every one of the 57 hunks changed nothing but colour on
this side.
FileListRow, ChannelPermissionDialog, CreateChannel{Drawer,Success} and
CreateKnowledgeSpaceDrawer were deleted by 2.6.0's unified-permission work;
our only edits to them were colour swaps, so the deletions stand (2.6.0 ships
a test asserting those paths are gone).
The 圆角与阴影 doc keeps this branch's 2026-07-31 filename and its §4
「例外与新增」 heading, and takes 2.6.0's new §3 on blur/frosted glass.
Both branches independently added violations to the same four legacy files,
pushing them past their frozen suppression counts; typed the five newcomers
rather than raising the ceilings, then pruned.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Toaster + toastStore now live in packages/ui and the client consumes
them instead of its own Recoil store, so knowledge-move undo and every
other caller share one implementation. Drops client store/toast.ts and
moveUndoToast.tsx, adds the rspress toast demo page and the tokens the
new component needs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The authorization panel had the illustration, the member list and the
three subject pickers had a bare grey line of text — so the same "there
is nothing here" read as two different states depending on which list
you were looking at, and the pickers' `py-4` left the message floating
near the top of a tall scroll area.
PermissionEmptyState holds the illustration + message and centers in
whatever height its parent gives it; all five lists consume it. Each
keeps its own copy — the pickers still distinguish no-results from
no-departments/no-groups.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The org / skill drill panels kept their pre-restyle back row — a
lucide chevron on slate greys, separated from the list by a hairline
that no other panel in this menu draws — and only the knowledge popup
had been switched to the list padding, so the "+" menu's drill panels
still doubled the gap under their last row.
The back row now uses Outlined.ArrowLeft on the menu's own greys with
a short divider before the title, drops the underline, and the list
padding rule keys off mobileTallPanel so every mobile list panel gets
it, not just the knowledge popup.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AiMessageBubble carried its own extension → bisheng-icon table, a copy
of what FileIcon already knows. FileIcon exports getFileTypeIcon and the
bubble consumes it, so a new file type is registered once.
Also deletes FileListRow and the knowledge mock: neither has had a
call site for a while (the file list renders through FileTable /
FileCard). Suppressions shrink accordingly. The share dialog picks up
the icon import while it is here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The "+" menu, knowledge pill, skill panel and the model / group selects
had each drifted: 8px vs 16px radius, per-menu padding, rows sized by
padding instead of height, and icon greys ranging over #999 / slate-600.
- Menus settle on rounded-2xl, 12px padding, 8px gap and 32px rows with
8px radius. Select grows a viewportClassName so its scroll viewport
can carry that padding instead of the outer panel (which would clip
the scrollbar).
- Resting icons all use #4E5969; active stays brand. The upload item
drops its baked-colour link.svg for Outlined.Attachment so it can
follow that tint.
- The "+" panel aligns to the input box's left edge rather than its own
trigger, which sits 13px inside it. The knowledge pill keeps trigger
alignment — mid-toolbar, where the input edge means nothing.
- Skill rows carry selection themselves (brand tint + trailing check)
now that the leading checkbox is gone; name and description each
clamp to one line and move the full text into a tooltip, but only
when measurement says they actually overflow. Empty search gets the
illustration instead of bare text.
- The knowledge panel takes its own 320px cap (skills/org sit at 440)
so it still opens downward more often, and the hand-rolled
useSubMenuLayout measuring goes away — align="center" plus Radix's
own collision handling already place these panels.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opening a row's options menu or its rename input moves the pointer off
the row — into the portaled menu — so the hover fill dropped away and
the row you were acting on stopped looking involved.
Both sidebars now pin the hover fill for as long as the row owns that
UI. It stays the hover fill, never the active row's: this row is not
the open conversation. On coarse pointers it applies too, where it
reads as "this row owns the menu" rather than as a hover affordance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rename is entered from a Radix dropdown item, and Radix restores focus
to the menu trigger as it unmounts — after our synchronous focus() call.
The input therefore never held focus, never fired blur, and clicking
away left the field open with the edit unsaved.
Two halves: re-assert focus once that restore has run (in the existing
selection timeout), and commit from a capture-phase pointerdown listener
so an outside click saves even when blur never fires. Since an outside
click now reaches the commit twice — the listener, then the input's own
blur — a ref mirrors isRenaming so the second one is a no-op instead of
a duplicate onRename. Tests cover both paths.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The copy link was underlined on hover and swapped its own label to
"copied" for two seconds. Both read as emphasis the sentence does not
want, and a label that changes under the cursor is easy to miss on the
one screen a user is likely to be screenshotting rather than reading.
The link now never underlines and never changes text; ErrorPage fires a
new `onCopied` callback instead, and the client raises its usual toast
(existing `com_ui_copied_to_clipboard` key, all three languages). The
package still carries no i18n and no toast of its own. `labels.copied`
and the now-dead `com_error_page.copied` key are gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ported from feat/2.6.0, where it was written in place: it is a
packages/ui component, so the library branch is where it belongs.
Both apps hand a support engineer the same identifiers, the same QR
payload and the same log file, so an engineer reading a user's
screenshot never has to know which app produced it.
Contract-clean: every string, the artwork and the "copied" confirmation
arrive as props/callbacks — the package carries no i18n and no toast.
The copy link is a plain brand-coloured button (no underline, no label
swap); the app confirms with its own toast via `onCopied`.
Adds qrcode.react to the workspace catalog for the QR block.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two hand-drawn knowledge glyphs (BookOpen / Books) predate the
matching bisheng-icons entries. Every call site — hub nav tab, the
attachment bar's KB card, the task-mode context chips — switches to
Outlined.Book / Outlined.Books, and the local SVG components are
deleted (ChatKnowledge only carried dead imports).
Sidebar section titles drop from bold to semibold, matching the weight
the rest of the sidebar chrome uses.
Suppressions: pruned the entries the deleted icons left behind.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The knowledge / skill submenus aligned their top edge with the parent
menu via a hand-computed alignOffset (measure the trigger, subtract
rects, re-measure on resize), and each panel repeated a heading that
its own trigger row already carried right next to it.
- DropdownMenuSubContent accepts align="center". Radix types SubContent
as 'start' | 'end' but hands the value straight to Popper, which does
support 'center', so the type is widened rather than the offset
hand-computed. The knowledge, org-knowledge and skill panels switch
to it and the alignOffset machinery goes away — useSubMenuLayout now
only clamps max height against the viewport.
- Panel headings drop where the trigger already names the list; the
mobile drill panels keep theirs, since there the heading IS the
back-navigation label. The knowledge pill's two variants collapse
into one branch with a mobile-only heading (the narrow toolbar can
render its trigger icon-only).
- Skill panels grow to 280×440 so the list isn't a letterbox.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rating an answer changed only the icon's highlight, which is easy to
miss and says nothing about what the rating is for. Every persisted
verdict — thumbs-up, or a dislike once its dialog is submitted — now
raises a "thanks, we'll use it to improve our answers" toast (new key,
all three languages).
Un-toggling stays silent: the icon losing its highlight is feedback
enough, and a toast there would read as if cancelling had itself been
recorded. onLike may now return the request promise, and the two call
sites do — so a failed request shows only the interceptor's error
toast, never both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- AccessModeSelector rows now behave like the options they are: hover
tint, brand-tinted background while checked, and the label kept on
one line so only the description truncates. Disabled rows opt out of
the hover tint so they still read as unavailable.
- That truncated description gets the same hover-only tooltip the
member list uses, so TruncatedTooltip moves out of PermissionListTab
into its own module. Its polymorphic ref keeps one `any` — now with
an inline reason — and the file's suppression entry shrinks.
- Both settings pages center their footer actions unconditionally; the
knowledge page had centered only in create mode, so the same footer
jumped between layouts depending on how you entered it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The member list, the create-page draft and the two "新增授权" dialogs had
each grown their own version of the same two things — so the level
control looked different depending on where you opened it, and the
draft picker was a look-alike of the share dialog rather than the same
dialog.
- PermissionLevelMenu extracts the list's dropdown (levels, separator,
destructive 移除) and the draft editor adopts it, dropping its
RelationSelect + separate delete icon button. Removal now always
lives inside the menu, never as a button beside it; a row with
neither permission renders the plain label at the same width, so the
column never jitters.
- permissionDialogStyles holds the share dialog's chrome verbatim —
shell sizing (80vh card / full-screen under 768px), subject tabs,
包含子部门 toggle, footer captions — and the draft picker consumes it
instead of re-approximating it.
- Empty authorization lists get their own copy (new key, three
languages) instead of borrowing the search-result empty text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>