Repository navigation
fix(app-shell): a record locked by an approval is shown locked to a reader outside that approval - #26402
Merged
Conversation
…approval The approvals list serves a request only to its participants, so a non-participant reader read an answered empty list while a lockRecord approval ran, and the record page offered inline edit the server then refused with RECORD_LOCKED. When the list finds no pending request, useRecordApprovals now reads client.approvals.getRecordLock and the page feeds a locked answer to resolveApprovalLock as the open approval. Claude-Session: https://claude.ai/code/session_01ADHkmQYqTcUW7GgPkfWCzw Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmQYqTcUW7GgPkfWCzw Co-authored-by: Claude <noreply@anthropic.com>
…ut a render-time ref Claude-Session: https://claude.ai/code/session_01ADHkmQYqTcUW7GgPkfWCzw Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
席位核对 · ACCEPT · seat
|
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.
Requested by Steve · project thread
Fixes #25934
Clause-②: no
Metadata-protocol: none
维护者速读
client.approvals.getRecordLock);答"锁了"就按锁定显示:编辑置灰、不给行内编辑、显示"审批锁定"横幅且不显示撤回按钮。ui/packages/app-shell/src/views/RecordDetailView.tsx(lockedForReader与撤回按钮的approvalIsSubmitter)、ui/packages/app-shell/src/hooks/useRecordApprovals.ts(startRead里的锁读取)、新测试RecordDetailView.approvalLock-25934.test.tsx。pnpm --filter @object-ui/app-shell exec vitest run --root ../.. packages/app-shell/src/views/RecordDetailView.approvalLock-25934.test.tsx,预期 4 个用例全绿。What changes
The approvals list (
GET /approvals/requests?object=…&recordId=…) serves a request only to its participants, so a member who can read a record but is not on its approval read that list as200 { data: [] }while alockRecord: trueapproval held the record. The record page took the answered empty read for "no approval is running" (rule 2 ofresolveApprovalLock) and offered inline edit, and the save answered409 RECORD_LOCKED.Ruling A (6422939157) added a lock signal every reader can see:
GET /approvals/records/:object/:recordId/lockandclient.approvals.getRecordLock(#26314, PR #26361), answering{ locked }only. This PR is the console half.useRecordApprovals(app-shell): when the list read finds no pending request, the shared read also callsclient.approvals.getRecordLock(object, recordId)through the mounted adapter's client (no hand-rolled URL), and the result carriesrecordLock: { locked } | null. A pending request the viewer can see still decides and the lock is not read. With no adapter mounted, the lock read is skipped. A lock read that never answered (501without the approvals service) leavesnull; a later failure keeps the last answer, as the rows are kept.RecordDetailView(lock region only):{ locked: true }from the record lock is fed toresolveApprovalLockas its open approval. That is rule 1's input exactly, since the server answerstrueonly when a pending request from a node whoselockRecordis notfalseexists. So the page reads locked and pending, Edit is disabled andcanEditis false. No lock answer means no open approval, so rule 2 is unchanged for a composition without the approvals service. For a lock known only from the record lock,approvalIsSubmitterisfalse: the reader cannot read the request, and its submitter always can, so the band offers no recall lever.ui/packages/react(resolveApprovalLock/ApprovalLockInputneed no new input, because the signal is rule 1's input) or toui/packages/plugin-detail(theDetailViewband computes from the host's threadedlocked/approvalPending/approvalIsSubmitter, it does not compute its own lock input). No sdui record is written: app-shell is not a registry package, and the per-package records are retired (ci(sdui): retire the per-package SDUI manifest records; CI's regeneration holds freshness (#26137 PR B) #26367).Clause-②: no: the claim graded ityes (widening)provisionally, on the assumption thatApprovalLockInputwould gain an input. It does not. The one type that gains a member, the hook's result interface, is not reachable from@object-ui/app-shell's entry:src/index.tsdoes not re-exportuseRecordApprovalsand the interface is not exported. This was measured on the source entry. The app-shelldistwas not built in this container.Tests
ui/packages/app-shell/src/views/RecordDetailView.approvalLock-25934.test.tsx(same probe asRecordDetailView.approvalLock-12220, record with noapproval_statusmirror, like showcase):200empty, lock{ locked: true }) getslocked,approvalPending,canEdit: false,approvalIsSubmitter: false, and the lock is read with the page's object and record id;{ locked: false }opens editable;501(no approvals service) opens editable, the answered empty read is not locked;lock_record: falserow decides (pending, editable) and the lock is not read.RecordDetailView.approvalLock-12220.test.tsx,useRecordApprovals.answered-12220.test.tsx(andresolveApprovalLock-12220.test.ts/DetailView.approvalReadAnswered-12220.test.tsx, whose packages this diff does not touch). No test edit was forced.Acceptance notes
GETper record open where the list finds no pending request. It goes through the adapter's authenticated, settle-counted client fetch.refresh(), and again when the mounted adapter changes (lockReaderis auseCallbackover[adapter], since 40ca935). The console'sAdapterProvidermounts the adapter before the record page reads.Verification
61c07986). 1:InlineEditContext.tsx:107rule 2 holds. 2:resolveApprovalLock({atRecordDetailView.tsx:1664andDetailView.tsx:1716. 3: the only lock source was the list read,useRecordApprovals.ts:480. 6:lifecycle-hooks.tsbeforeUpdate:538,lockedError(recordLockRefusal:633, refusal text:294. 7:lock_record?: booleanatapproval-service.ts:336. 9: PR fix(app-shell,react,plugin-detail): fold an override-only admin's approval levers into one menu and draw Recall once #26053 merged (054652bf), so the serial condition is met. Line numbers drifted, and every substance held.7d11d10e(main merged after fix(app-shell): a member takes back their own comment reaction through the unreact door #26384; 40ca935 drops an unusedactimport, the Lint UI red, and the hook's render-time ref write): one locked run after a fresh install:turbo run build --filter='@object-ui/app-shell^...'63/63,turbo lint --filter=@object-ui/app-shell0 errors, app-shell type-check exit 0, 82 neighbouring files / 554 tests,VERDICT command-exit 0. Earlier, onfca25b0b, one locked run:turbo run build --filter='@object-ui/app-shell^...'(64/64), thenpnpm --filter @object-ui/app-shell type-check(src andtsconfig.test.json), then 4 files: the new test,RecordDetailView.approvalLock-12220,useRecordApprovals.answered-12220,RecordDetailView.approvalRecallGate. Test Files 4/4, Tests 25/25,VERDICT command-exit 0.e0f9e560): all 71RecordDetailView.*.test.tsx, theuseRecordApprovals*hook tests,RecordApprovalsPanel*andAppContent.recordSaveToastNamesRecord-26172. Test Files 82/82, Tests 551/551,VERDICT command-exit 0. The full app-shell suite is left to CITest UI.useRecordApprovals.tsandRecordDetailView.tsxwere restored to the base61c07986(on disk:getRecordLock3→0,lockedForReader3→0). The new test's non-participant case went red at:235(expected 'false' to be 'true',locked). Both lock-read cases went red becausegetRecordLockwas never called. The participant control stayed green: Tests 3 failed / 1 passed,VERDICT command-exit 1. Restored withgit checkout HEAD --:git diff HEADempty, blob hashes equal HEAD's. app-shell tests import the source directly, so no dist leg applies.7d11d10e:dispatch-gates --commandsderived 34 (main moved), all 34 exit 0, 0 NOT-MEASURED. Onfca25b0b: derived 35. All 35 exit 0,check:type-check-debtincluded.--ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Roster gate under.changeset:check-changeset-fixed.mjsexit 0. All other families: this PR's check-runs.Generated by Claude Code