Repository navigation
feat(console): tell the comment author, in one sentence, which mentioned users will not be notified; draw picked mentions as chips - #28261
Conversation
…ot be notified The adapter's create/update take an optional per-call onMentionsWithheld listener that hears the write answer's withheldMentions; the record page's three comment writes name those users in one sentence, mapped through the picker's suggestions. The composer draws picked mentions as chips. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvjxjPWDhWVKzkEU2x
…'s three comment writes, and the composer's mention chips Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvjxjPWDhWVKzkEU2x
… changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvjxjPWDhWVKzkEU2x
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #28218 (body, triage 6530136006, claim 6531972369, os-dev-report 6533064566), ruling A on #27703 (6523848208) and its spec half PR #28174, the PR body, the 21-file diff against merge base 86cf7ce, the head's check-runs (read 2026-10-30T21:07Z: check-runs on fcdb4c4: 60 total, 45 success (Auto Label, Build & E2E UI, Build UI Deps, Check Changeset, Check PR Size, Dogfood Regression Gate, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Test Core, Test UI, Test UI (all 16), Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter); 15 skipped path-filtered (18.x upgrade harness (opt-in), Auto Label, Build Core, Build Docs, Check PR Size, Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Plan Test Core shards, Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), Test Core (matrix shard/8), UI Published Artifacts); 0 failure, 0 in progress). Author ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN 席位核对ACCEPT · seat 席位意见:在记录讨论里 @ 了看不到这条记录的同事时,评论照常发出,同时弹出一句提示,点名谁不会收到通知,不说原因。发评论、回复、编辑新增 @ 三处都覆盖。输入框下方把已 @ 的人显示为标签,预览里的 @姓名 也显示为标签。这是 #27703 裁决 A 的控制台一半,服务端一半是已合并的 #28174。
|
Requested by Tesla · project thread
Fixes #28218
Refs #27703
Clause-②: yes (widening:
@object-ui/data-objectstackObjectStackAdapter.creategains an optional third parameteropts.onMentionsWithheld, and.update's existingoptsgains the same optional member; both still resolve the record, and no export, key or route is added)Metadata-protocol: none
维护者速读
withheldMentions),但控制台一直不读,作者以为对方已被通知。create/update多了一个可选的逐次回调,返回值不变,其他调用方不受影响;回调抛错不会让已保存的评论被当成失败。回滚即 revert 本 PR,服务端不受影响。ui/packages/app-shell/src/views/RecordDetailView.tsx(tellMentionsWithheld与三个评论写回调);ui/packages/data-objectstack/src/index.ts(notifyWithheldMentions);ui/packages/plugin-detail/src/RichTextCommentInput.tsx(标签行与预览)。pnpm --dir ui exec vitest run packages/app-shell/src/views/RecordDetailView.withheldMentions-28218.test.tsx packages/data-objectstack/src/withheldMentions-28218.test.ts packages/plugin-detail/src/__tests__/RichTextCommentInput.mentionChips-28218.test.tsx,预期 3 个文件、14 个测试全绿。What changes
The answer reaches the caller (
ui/packages/data-objectstack/src/index.ts). Measured first:ObjectStackAdapter.create/.updateresolvedresult.recordand dropped every other member of the write answer, sowithheldMentionsnever reached the record page. Both methods now take an optional per-call listener,opts.onMentionsWithheld(ids)(onupdatebesideifMatch, which still reaches the client alone). A module helper,notifyWithheldMentions, reads the answer structurally, keeps the string ids, and calls the listener once after the write succeeded, only when at least one id is named. A runtime that predates the key, or a write that withheld nobody, calls nothing. A throwing listener is isolated like the subscriber fan-outs, so a stored comment is never reported as failed. What every caller resolves is unchanged. A per-call listener was chosen over the globalonWriteWarningchannel because the sentence needs the picker's names, which only the writing page holds.One sentence, no reason (
ui/packages/app-shell/src/views/RecordDetailView.tsx).tellMentionsWithheldmaps the ids to names through the mention picker's suggestions (the list the write'smentionswere extracted from) and raises onetoast.warning:detail.mentionsWithheld, "NAMES will not be notified of this comment.", the names joined with the pack'svalidation.formInvalidJoiner, the list punctuation the record feed already uses. The threesys_commentwrites pass it:handleAddComment,handleAddReplyandhandleEditComment. Per the ruling the sentence gives no reason (one bit per named user), and nothing probes a colleague's access before the write (B was not taken).Mention chips (
ui/packages/plugin-detail/src/RichTextCommentInput.tsx). Under the editor, the users the text mentions are a list of chips labelleddetail.mentionedPeople, resolved with the sameextractMentionsthe write uses, so the row shows exactly whom the comment'smentionswill carry, multi-word names whole. In the preview, each picked@NAMEis one chip (longest name first, not followed by a letter or digit, asextractMentionsmatches); any other@wordkeeps the plain highlight it had. The preview escapes the text before any markup, chips included.Copy.
detail.mentionsWithheldanddetail.mentionedPeoplein all ten packs. The sentence is phrased so it needs no plural forms in any pack.detail.mentionedPeopleis also inDETAIL_DEFAULT_TRANSLATIONS(the provider-less mirror of theenpack).File surface. Within the order's list:
RecordDetailView.tsx(the three callbacks and one helper),RichTextCommentInput.tsx,data-objectstack/src/index.ts, the ten packs, tests and the root changeset. Outside it, both small:plugin-detail/src/useDetailTranslation.ts(one row, the chip row's label, which the defaults-mirror test requires beside the pack) andui/packages/data-objectstack/README.md(the method list names the new option, perui/AGENTS.md#2). Nopackages/spec/**and no server change.Serial constraints. Neither #28116 nor #27713 has landed (both open at the time of writing); this branch is on
main86cf7cef. The edits inRecordDetailView.tsxare the helper and three call sites, in hunks neither PR touches as far as a read of their descriptions shows; whichever lands first, this branch mergesorigin/mainand resolves.Tests
New:
ui/packages/data-objectstack/src/withheldMentions-28218.test.ts(4):createhands the ids to the listener once and still resolves the plain record, and the listener is never forwarded to the client;updatehands them on andifMatchstill reaches the client alone (no options bag without it); CONTROL: an answer without the key, with an empty list, or with no string ids calls nothing; a write without the listener resolves as before, and a throwing listener does not reject a stored write.ui/packages/app-shell/src/views/RecordDetailView.withheldMentions-28218.test.tsx(5), through the realRecordDetailView, its composer, reply input and edit form, under a realI18nProviderwith theenpack read by key: a comment names the withheld users by their picker names (a multi-word name included) and is posted; only the withheld users are named; CONTROL: a comment whose mentions all reach their users says nothing; a reply names them; an edit that adds a mention names them.ui/packages/plugin-detail/src/__tests__/RichTextCommentInput.mentionChips-28218.test.tsx(5): the chip row names each picked user once, multi-word names whole; it follows the text and is absent with no mention; a prefix of a longer name or an@wordnaming nobody is no chip; the preview draws a picked@NAMEas one chip and keeps other@words plain; the preview escapes the text.Updated: three assertions that pinned the
sys_commentcreate's exact arguments now include the per-call listener (commentWriteFailure-10899×2,feedRecordScope). Their behaviour is unchanged.Ablations (each through
scripts/ablation-replace.mjs, mutation proved on disk, restored to the HEAD blob):RecordDetailView.tsx, the comment write withoutonMentionsWithheld→RecordDetailView.withheldMentions-282182 failed / 3 passed (the two comment cases); restored, blob6cf08c0a= HEAD.data-objectstack/src/index.ts,notifyWithheldMentionsnot calling the listener →withheldMentions-282183 failed / 1 passed (the control stays green, as it should); restored, blob20fba8d2= HEAD.Acceptance notes
pnpm dev -- --fresh, showcase, at this branch's build, 2026-10-30T20:11Z). As the seeded admin, a user created throughPOST /api/v1/auth/admin/create-userwith no membership (membershipCreated: false) cannot read the org's records.POST /api/v1/data/sys_commenton ashowcase_projectrecord mentioning that user andusr_showcase_auditor_demoansweredwithheldMentionswith both ids; a comment with no mentions answered no such key (id,object,recordonly); aPATCHadding the user to a comment that had no mention answeredwithheldMentionswith that id. Through the realObjectStackAdapter(builtdist) and@objectstack/clientagainst the same server:createwith the listener heard[that id]and resolved the record without the key; anupdatethat added no mention heard nothing. The server was stopped after the measure.toast.warningafter a successful write: the comment is posted, so it is not an error.Verification
Local runs
Head
fcdb4c48, onmain86cf7cef.pnpm exec turbo run build(whole repo, under the verify lock) →Tasks: 116 successful, 116 total, VERDICT command-exit 0.type-checkof@object-ui/data-objectstack,@object-ui/plugin-detail,@object-ui/app-shell(eachtsc --noEmit, plustsconfig.test.jsonwhere the script runs it) → exit 0 each.ui/that namesys_comment,mentionSuggestions,RichTextCommentInputorextractMentions):pnpm --dir ui exec vitest run packages/data-objectstack/ packages/plugin-detail/plus those app-shell and console test files, the new record-page file anddefaults-maps-mirror-en-pack→Test Files 473 passed | 1 skipped (474),Tests 4547 passed | 18 skipped (4565), VERDICT command-exit 0.pnpm --dir ui exec vitest run packages/i18n/ packages/app-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx→Test Files 93 passed (93), exit 0.ts/tsxfiles underui/→ 0 errors.pnpm --dir ui run check:new-line-citations→ 0 new citations, exit 0.86cf7cef; neither feat(plugin-detail)!: one record page laid out by its own width; 动态 is the right column's first panel #28116 nor feat(rest,console): one request opens a record page #27713 is among them, andgit merge-treewithorigin/mainis clean (the ten packs are shared paths, in other hunks).Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackonfcdb4c48→ 35 commands, each run with stdin from /dev/null and its exit code captured before any pipe;--ranreconcile:35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Two more beyond the derivation, the Type Check UI i18n legs.Generated by Claude Code