feat: batch of small UX optimizations - #29
Conversation
- The site-admin "viewing as a member" team banner gains a dismiss button; dismissal is session-only and resets when the view toggles - Info bars render markdown (new MarkdownText component): scope grants notice, image proxy subtitle, and the team domains / sub-teams / groups hints are unified to the info-bar style under horizontal tabs, with the sub-teams "create" button moved below the bar (still right-aligned) - Admin users bulk bar: the clear button is replaced by an X taking the intent icon's place on the left - Avatars in My Apps / Teams / Team members / Authorized apps copy the app client id / team id / user id on click (new CopyIdTrigger) - Team app cards: name and description on separate lines; an empty description no longer falls back to the client id - Linked accounts: truncated username / id rows show the full value in a tooltip on hover - App webhooks receive one final `webhook.deleted` delivery before the row is removed, so endpoints can learn they were deleted - Admin login errors: the user-agent column shows the parsed browser label; the full UA stays available on hover - Admin image proxy: ids render in full and truncate with a CSS ellipsis only when the column is too narrow - Pagination bars pin to the bottom of the page content (still above the legal footer) regardless of how few items the list holds
审查者指南此 PR 汇集了通知、管理表格、标识符交互、团队视图和分页方面的小幅 UX 改进,同时新增了一个删除前的签名 webhook 生命周期事件及相应文档。UI 改动主要引入了可复用的文案/Markdown 组件和基于 flex 的页面布局;后端则会在删除活动 webhook 记录之前发送并记录 webhook 删除告别事件投递时序图sequenceDiagram
participant Client
participant API
participant WebhookEndpoint
participant Database
Client->>API: DELETE /api/apps/:appId/webhooks/:webhookId
API->>Database: SELECT app_webhooks row
alt webhook is active
API->>WebhookEndpoint: POST webhook.deleted with signature
WebhookEndpoint-->>API: HTTP response
API->>Database: INSERT app_webhook_deliveries
end
API->>Database: DELETE app_webhooks row
API-->>Client: Deletion response
点击复制标识符交互时序图sequenceDiagram
actor User
participant CopyIdTrigger
participant Clipboard
participant UI
User->>CopyIdTrigger: Click or press Enter/Space on avatar
CopyIdTrigger->>Clipboard: navigator.clipboard.writeText(id)
Clipboard-->>CopyIdTrigger: Copy completed
CopyIdTrigger->>UI: Show copied tooltip
CopyIdTrigger-->>User: Keep surrounding navigation from firing
文件级变更
提示和命令与 Sourcery 交互
自定义你的使用体验访问你的仪表板以:
获取帮助Original review guide in EnglishReviewer's GuideThis PR packages small UX improvements across notices, admin tables, identifier interactions, team views, and pagination, while also adding a pre-deletion signed webhook lifecycle event with matching documentation. The UI changes primarily introduce reusable copy/Markdown components and flex-based page layouts, and the backend sends and records webhook.deleted before deleting active webhook records. Sequence diagram for webhook deletion farewell deliverysequenceDiagram
participant Client
participant API
participant WebhookEndpoint
participant Database
Client->>API: DELETE /api/apps/:appId/webhooks/:webhookId
API->>Database: SELECT app_webhooks row
alt webhook is active
API->>WebhookEndpoint: POST webhook.deleted with signature
WebhookEndpoint-->>API: HTTP response
API->>Database: INSERT app_webhook_deliveries
end
API->>Database: DELETE app_webhooks row
API-->>Client: Deletion response
Sequence diagram for click-to-copy identifier interactionsequenceDiagram
actor User
participant CopyIdTrigger
participant Clipboard
participant UI
User->>CopyIdTrigger: Click or press Enter/Space on avatar
CopyIdTrigger->>Clipboard: navigator.clipboard.writeText(id)
Clipboard-->>CopyIdTrigger: Copy completed
CopyIdTrigger->>UI: Show copied tooltip
CopyIdTrigger-->>User: Keep surrounding navigation from firing
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Deploying prism-docs with
|
| Latest commit: |
a157e84
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e7e68221.siiway-prism.pages.dev |
| Branch Preview URL: | https://feat-ui-optimizations.siiway-prism.pages.dev |
There was a problem hiding this comment.
嗨——我发现了 3 个问题
面向 AI Agent 的提示
请处理此次代码审查中的评论:
## 个别评论
### 评论 1
<location path="src/components/MarkdownText.tsx" line_range="45-49" />
<code_context>
+ },
+});
+
+export function MarkdownText({ source }: { source: string }) {
+ const styles = useStyles();
+ const [html, setHtml] = useState("");
+
+ useEffect(() => {
+ let cancelled = false;
+ // renderMarkdown registers any <img> with the image proxy, so it is async.
+ void renderMarkdown(source).then((out) => {
+ if (!cancelled) setHtml(out);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [source]);
+
+ return (
+ <span
+ className={styles.body}
+ // Sanitized by renderMarkdown (DOMPurify, conservative allowlist).
+ dangerouslySetInnerHTML={{ __html: html }}
+ />
+ );
</code_context>
<issue_to_address>
**问题 (bug_risk):** MarkdownText 将 `p`、`ul` 和 `pre` 等块级 Markdown 元素渲染在 `<span>` 内部,这属于无效的短语内容标记;浏览器会重新解析或移动这些元素,导致 MessageBar 的布局和样式无法可靠地与渲染后的 Markdown 保持一致。
**触发条件:** 翻译后的信息栏字符串包含段落、列表或其他块级 Markdown 时。
**建议修复:** 将经过清理的 HTML 渲染在 `div` 等块级容器中,或者将 Markdown 输出限制为行内元素。
```suggestion
<div
className={styles.body}
// Sanitized by renderMarkdown (DOMPurify, conservative allowlist).
dangerouslySetInnerHTML={{ __html: html }}
/>
```
</issue_to_address>
### 评论 2
<location path="src/components/CopyIdTrigger.tsx" line_range="47-50" />
<code_context>
+ const [copied, setCopied] = useState(false);
+
+ const copy = () => {
+ void navigator.clipboard.writeText(id).then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ });
+ };
+
</code_context>
<issue_to_address>
**问题 (bug_risk):** `navigator.clipboard.writeText(id)` 被拒绝时,其 Promise 会被丢弃,因此剪贴板权限失败或不安全上下文导致的失败会产生未处理的 Promise 拒绝,并使触发器无提示地继续显示未复制成功的状态。
**触发条件:** 浏览器拒绝剪贴板访问,或页面不处于安全的剪贴板上下文中时。
**建议修复:** 捕获拒绝,并显示现有的错误/Toast 反馈,而不是丢弃该错误。
</issue_to_address>
### 评论 3
<location path="src/components/Pagination.tsx" line_range="24-25" />
<code_context>
+ // Pin the bar to the bottom of the page's flex column regardless of how
+ // few rows the list holds; paddingTop keeps the gap when there is no
+ // free space to absorb.
+ marginTop: "auto",
+ paddingTop: "16px",
},
pageCount: {
</code_context>
<issue_to_address>
**问题 (bug_risk):** 向分页元素添加 `marginTop: "auto"`,并不能在其父级不是垂直 flex 容器的布局中将分页固定到底部;例如,AdminDatabase 的 `pane` 和 `resultMeta` 父级仍是普通布局/行方向的 flex 布局,因此当页面行数较少时,分页仍会紧接在表格之后。
**触发条件:** 在页面或嵌套面板中使用分页,而其父级没有能够垂直扩展的 flex-column 容器时。
**建议修复:** 将所有包含分页的页面/面板设为填满可用内容高度的 flex column,或者将底部固定布局应用在页面容器上,而不仅仅应用于 Pagination。
</issue_to_address>请帮我变得更有用!请点击每条评论旁的 👍 或 👎,我会利用这些反馈来改进审查结果。
Original comment in English
Hey - I've found 3 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/components/MarkdownText.tsx" line_range="45-49" />
<code_context>
+ },
+});
+
+export function MarkdownText({ source }: { source: string }) {
+ const styles = useStyles();
+ const [html, setHtml] = useState("");
+
+ useEffect(() => {
+ let cancelled = false;
+ // renderMarkdown registers any <img> with the image proxy, so it is async.
+ void renderMarkdown(source).then((out) => {
+ if (!cancelled) setHtml(out);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [source]);
+
+ return (
+ <span
+ className={styles.body}
+ // Sanitized by renderMarkdown (DOMPurify, conservative allowlist).
+ dangerouslySetInnerHTML={{ __html: html }}
+ />
+ );
</code_context>
<issue_to_address>
**issue (bug_risk):** MarkdownText renders block-level markdown elements such as `p`, `ul`, and `pre` inside a `<span>`, which is invalid phrasing-content markup; the browser reparses or relocates those elements and the MessageBar layout and styles do not reliably match the rendered markdown.
**Triggers:** When a translated info-bar string contains a paragraph, list, or other block-level markdown.
**Suggested fix:** Render the sanitized HTML inside a block container such as a `div`, or restrict the markdown output to inline elements.
```suggestion
<div
className={styles.body}
// Sanitized by renderMarkdown (DOMPurify, conservative allowlist).
dangerouslySetInnerHTML={{ __html: html }}
/>
```
</issue_to_address>
### Comment 2
<location path="src/components/CopyIdTrigger.tsx" line_range="47-50" />
<code_context>
+ const [copied, setCopied] = useState(false);
+
+ const copy = () => {
+ void navigator.clipboard.writeText(id).then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ });
+ };
+
</code_context>
<issue_to_address>
**issue (bug_risk):** A rejected `navigator.clipboard.writeText(id)` promise is discarded, so clipboard permission failures or insecure-context failures produce an unhandled promise rejection and leave the trigger silently showing that nothing was copied.
**Triggers:** When the browser denies clipboard access or the page is not in a secure clipboard context.
**Suggested fix:** Catch the rejection and show the existing error/toast feedback instead of discarding it.
</issue_to_address>
### Comment 3
<location path="src/components/Pagination.tsx" line_range="24-25" />
<code_context>
+ // Pin the bar to the bottom of the page's flex column regardless of how
+ // few rows the list holds; paddingTop keeps the gap when there is no
+ // free space to absorb.
+ marginTop: "auto",
+ paddingTop: "16px",
},
pageCount: {
</code_context>
<issue_to_address>
**issue (bug_risk):** Adding `marginTop: "auto"` to the pagination element does not pin it to the bottom for layouts whose parent is not a vertical flex container; for example, the AdminDatabase `pane` and `resultMeta` parents remain ordinary/row flex layouts, so pagination still sits immediately after the table when a page has few rows.
**Triggers:** When using pagination in a page or nested pane that has no vertically growing flex-column parent.
**Suggested fix:** Make every pagination-containing page/pane a flex column that fills the available content height, or apply the bottom-pinning layout at the page container rather than only on Pagination.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
Preview deployed: https://prism-preview.siiway.workers.dev (shared preview Worker + database, so the newest PR deploy is what is live there). |
…db pagination pinning - MarkdownText renders sanitized markdown in a <div> instead of a <span>; block-level output (<p>/<ul>/<pre>) is no longer invalid phrasing content - CopyIdTrigger catches navigator.clipboard rejections and surfaces a "Copy failed" tooltip instead of an unhandled rejection / silent no-op - AdminDatabase browse pane now fills the content height (flex chain + 1fr grid row, pane alignSelf:stretch) and pins the row-count/pagination bar to the bottom like the other list pages; sidebar stays top-aligned
Same unhandled-rejection / silent-failure pattern as CopyIdTrigger: catch navigator.clipboard rejections and show a "Copy failed" tooltip (and a dismiss icon) instead of dropping the error.
…tros - MarkdownText forces white-space:normal and overflow-wrap:anywhere so the MessageBar body wraps instead of overflowing the viewport (the reflow auto-detection ran against the initial empty markup and never re-fired) - Admin → OAuth Sources: drop the "OAuth Sources" heading and render the hint as an info bar to match the rest of the admin surface - Admin → Notices: intro hint moved into an info bar; the "New notice" button moves below it (still right-aligned), following the sub-teams pattern from the earlier commit
Matches the pattern already used by Admin → Users: an Input + Search button toolbar filters the list server-side, resetting to page 1 on submit. Enter in the input triggers the search. - worker /admin/apps: accepts ?search=…, LIKE-matches on app name, client id, owner username, and team name - worker /admin/teams: accepts ?search=…, LIKE-matches on team name and description - adminListApps / adminListTeams API clients grow a `search` argument - Admin → Teams merges the search toolbar with the existing "Create team" button so they share the row
- CopyIdTrigger takes an optional `copiedLabel` that flashes on success;
the four avatar-copy call sites (My Apps, Teams, Team members,
Authorized apps) each pass a role-specific string, so the confirmation
says *what* was copied ("Copied team ID") instead of a generic "Copied!"
- Pagination jump input's placeholder becomes "Page" / "页数" — the
previous "Go to page" / "跳转到页" was truncated inside the narrow
64-px input, leaving the visible hint identical to the Go button next
to it. The Go button label and the input's aria-label are unchanged.
一组小优化,合并为单个 commit。
变更内容
团队横幅
信息框
MarkdownText组件(走与用户内容相同的 marked + DOMPurify 管线)。作用域授权的提示(site:*、site:team:*等代码片段)现在正确渲染。管理面板
parseClient),hover 显示完整 UA。slice(0,12))。头像点击复制 ID
团队应用卡片
关联账号
Webhook
webhook.deleted事件(不受订阅范围限制,停用状态除外),让接收端得知自己被删除。投递记录照常写入。分页
验证
bun run build(tsc -b + vite build)✓bun run lint✓bunx tsc -p tsconfig.app.json --noEmit/bunx tsc -p tsconfig.worker.json --noEmit✓bun run docs:build✓Sourcery 总结
通过更清晰的信息指导、改进标识符处理、更一致的分页方式以及 Webhook 删除通知,优化管理和团队管理体验。
新功能:
webhook.deleted通知。错误修复:
改进:
文档:
webhook.deleted投递行为。Original summary in English
Sourcery 摘要
通过更清晰的指导、更便捷的标识符处理、一致的分页方式和 Webhook 删除通知,改进团队、管理和集成工作流。
新功能:
webhook.deleted通知。错误修复:
改进:
文档:
webhook.deleted交付行为。Original summary in English
Sourcery 摘要
通过提供更清晰的指导、更便捷的标识符处理方式、统一的分页体验和 Webhook 删除通知,改进团队、管理和集成工作流。
新功能:
webhook.deleted通知。错误修复:
改进:
文档:
webhook.deleted投递行为。Original summary in English
Sourcery 总结
通过提供更清晰的指导、更便捷的标识符处理方式、统一的分页功能、可搜索的管理员列表以及 Webhook 删除通知,改进行政管理和团队管理工作流。
新功能:
webhook.deleted事件,并记录该事件的投递情况。错误修复:
改进:
文档:
webhook.deleted事件投递行为。Original summary in English
Summary by Sourcery
Improve administrative and team-management workflows with clearer guidance, easier identifier handling, consistent pagination, searchable admin lists, and webhook deletion notifications.
New Features:
webhook.deletedevent before removing active webhooks and record the delivery.Bug Fixes:
Enhancements:
Documentation:
webhook.deleteddelivery behavior in English and Chinese webhook documentation.