feat(bpm-core-react): surface approval comments and allow multi-person ad-hoc targets - #36
Open
coollugi wants to merge 4 commits into
Open
feat(bpm-core-react): surface approval comments and allow multi-person ad-hoc targets#36coollugi wants to merge 4 commits into
coollugi wants to merge 4 commits into
Conversation
…meline
Every part of a timeline entry was appended to one caption-sized run joined by
" · ", so what an approver wrote sat between the node label, the actor, the
timestamp and the signature hash:
節點:簽核節點 1 · 操作者:陳財務經理 · 時間:… · 決議:同意 ·
同意說明:測試 · 簽章:已驗證(5f412a0ccbe2…)
`ActivityStepDescriptionPart` gains a `comment` member, and the four decision
comments (approve / reject / return / transfer) now use it. The step renders
metadata inline as before and each comment on its own line at `body` size with
its label and a left rule, red for a rejection.
`whiteSpace: pre-wrap` keeps line breaks a person typed, which the joined run
also flattened.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018STtLMQTp1GVCYFp9GPSfg
`AdhocTargetInput.memberIds` has always been a list, but the picker was `mode="single"` and the handler sent `[selectedMember.id]`, so adding three countersigners meant opening the dialog three times. The picker is now `mode="multiple"` with `overflowStrategy="wrap"` so the chosen names stay visible, and the selection maps straight onto `memberIds`. Going multiple also picks up `clearable`, which this component's single mode never wired up. `menuMaxHeight` is set while the picker is open anyway: the list is the member directory, which has no upper bound, and without it the menu grows past the viewport with no scrollbar. The "which target do we send" decision moved into `readAdhocTargetDraft()` in `shared.ts` so it can be tested without rendering the dialog; that is the part that changed shape here. `onSearchTextChange` is dropped from this picker. It called `readUniqueMemberOption` to auto-select when the typed text matched exactly one option, which is a single-selection convenience — under multiple selection it would fight the list the approver is building. The transfer picker still uses it and is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018STtLMQTp1GVCYFp9GPSfg
…omment Moving an approval comment onto its own line dropped two things the inline `dangerText` part used to carry. The label and the text were rendered adjacent with only a 4px margin between them, so the timeline read "拒絕原因資料不足,請補件" — the margin is invisible to `textContent`, and every other part of a step still reads "X:Y". The margin goes with it: ":" already carries its own trailing half-em. The danger tone only overrode `borderLeftColor`, so a rejection reason lost `--mzn-color-text-error` and became indistinguishable from an approval note. It now keeps that colour, label included. Writing the border as longhands is defensive rather than a fix — the spread already put the `borderLeftColor` override after the shorthand, so the result was correct — but it removes the dependency on that ordering. Also drops the inline `dangerText` part this replaced: the comment part left it with no callers, so `readDangerTextDescriptionPart`, the variant itself, `HISTORY_DANGER_TEXT_STYLE` and its renderer branch were all unreachable. Adds a render spec for the separator and both tones, since the only cover they had was e2e. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…xports it `@rytass/bpm-core-client` has no root export for it, so the spec failed `tsc -p tsconfig.spec.json` even though jest was green — a type-only import is erased before ts-jest ever resolves it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent UI changes, one commit each, both from the same customer UAT
round as #35. They touch the instance detail view only — no engine, schema or
GraphQL change.
1. An approval comment gets its own line (
2b03494)Reported as "簽核意見不明顯,跟簽核歷程的其他資訊混在一起,且字很小".
Every part of a timeline entry was appended to one
variant="caption"runjoined by
" · ", so what a person wrote sat between the node label, theactor, the timestamp and the signature hash:
ActivityStepDescriptionPartgains acommentmember; the four decisioncomments (approve / reject / return / transfer) use it, and the step renders
metadata inline as before with each comment on its own line at
bodysize.whiteSpace: pre-wrapkeeps line breaks the writer typed, which the joined runflattened.
renderActivityDescriptionPartnow takesExclude<…, { type: 'comment' }>so"comments never go inline" is a type error rather than a convention.
2. Ad-hoc targets accept several people (
4543bee)AdhocTargetInput.memberIdshas always been a list, but the picker wasmode="single"and the handler sent[selectedMember.id], so adding threecountersigners meant opening the dialog three times.
Now
mode="multiple"withoverflowStrategy="wrap". Two things came along:menuMaxHeight— the options are the member directory, which has noupper bound; without it the menu grows past the viewport with no scrollbar.
clearablestarts working, since this component's single mode neverwired it up.
onSearchTextChangeis dropped from this picker only. It auto-selected whenthe typed text matched exactly one option, which is a single-selection
convenience that would fight a list being built up. The transfer picker keeps
it and is untouched.
Tests
New
sections/shared.spec.ts, 10 cases over two pure functions(
readActivityDetailParts,readAdhocTargetDraft). Both headline assertionswere checked against a deliberately broken implementation first — the comment
case fails when the comment is emitted as a
textpart, the multi-member casefails when the draft slices to the first id.
Building the target moved into
readAdhocTargetDraft()so that decision istestable without rendering the dialog; it is the part whose shape changed.
Full suite green (
pnpm test, 6 projects),pnpm typecheckclean,pnpm lint0 errors — the 5 warnings are pre-existing and in other files.
What I did not do, and why it needs your call
Targeting a department or a position from the ad-hoc dialog. The customer
asked to pick countersigners by employee number or department. The engine is
ready —
AdhocTargetKindEnumalready hasORG_UNIT_MEMBER(withincludeDescendants) andPOSITION, andOrgUnitPicker/PositionPickeralready exist in
components/admin-pickers.tsx.The blocker is data access: both pickers take a pre-fetched list, and the only
query that returns org units and positions is
organizationDashboard, whoseresolver is
@BPMAdminOnly(). An ordinary approver cannot read it, so wiringit into an approver-facing dialog would either fail for the people who need it
or require widening that resolver.
That looks like an API design decision rather than a UI one, so I stopped here
rather than inventing a query shape. Happy to follow whatever you prefer — a
narrow non-admin query for picker data, scoping the existing one, or something
else.
Searching members by employee number or department is the other half of the
same request and is equally not a UI change:
searchMembers(searchText: String!)is one free-text argument whose matching fields are decided by the host's
BPMMemberResolver.search.One more thing you will know better than me: the member picker has
disabledOptionsFilterbut notclearSearchText={false}. In a repo I work in,that combination made mouse-selection after typing silently fail while keyboard
Enter worked. I left it alone because I could not reproduce it here and the
flag has its own downside, but it may matter more now that a person clicks
several options in a row.
🤖 Generated with Claude Code
https://claude.ai/code/session_018STtLMQTp1GVCYFp9GPSfg