Uh oh!
There was an error while loading. Please reload this page.
feat(detail): record-level inline edit — shared InlineEditContext + one atomic Save (#2407 P1) - #2542
Merged
Merged
Conversation
…ne atomic Save (#2407 P1) Lift the inline-edit session out of DetailView's private state into a shared, record-level context so the details body (and, in P2, the highlights strip) share ONE draft and commit it in ONE atomic, cross-field-validated write — replacing the per-field save loop (objectui#2407 P1, follow-up to #2402 / #2529). - InlineEditContext / InlineEditProvider / useInlineEdit (@object-ui/react): pure UI state (editing / canEdit / draft / autoFocusField / saving / error + enter / setField / cancel / reset). Separate from RecordContext (mirrors HighlightFieldsContext) so per-keystroke draft churn doesn't re-render other record:* consumers. - <InlineEditSaveBar> (plugin-detail): the record-level sticky Save/Cancel bar. DataSource mode issues ONE update(obj, id, draft, { ifMatch: updated_at }) → refresh; a 409 reuses ConcurrentUpdateDialog (reload / overwrite). Callback mode loops the drawer's per-field onFieldSave (plugin-gantt/calendar/kanban). - DetailView now consumes useInlineEdit() (read-only without a provider); its header/inline Save-Cancel bars + per-field batch save are removed; the approval-lock badge is retained. - record:details + RecordDetailDrawer each wrap their DetailView in an InlineEditProvider + InlineEditSaveBar. The object-lifecycle / permission gate flows through canEdit. Highlights stay read-only (P2 makes them editable). Guardrails preserved: computed (formula/summary/rollup/auto_number) + readonly + system fields expose no editor; canEdit gate; OCC ifMatch + dialog; the atomic partial update carries only user-edited keys (never computed/read-only). Verified: react + plugin-detail (vite) builds clean; plugin-detail type-check clean; 200 tests pass (21 files) incl. new InlineEditContext + InlineEditSaveBar suites (atomic single-update, 409 -> dialog, callback mode, cancel) and the updated record:details / drawer suites. app-shell type-check is clean w.r.t. this change (only a pre-existing, unrelated missing-module error remains). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y1f7TvqYMo41g9RbQ6H2od
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 15, 2026 16:42
Uh oh!
There was an error while loading. Please reload this page.
4 tasks
os-zhuang added a commit
that referenced
this pull request
Jul 16, 2026
…aft (#2549) objectui#2407 P2 (final phase). Makes the highlights strip editable in place, sharing ONE draft + ONE atomic Save with the details body. HeaderHighlight consumes useInlineEdit() (hover-pencil/double-click enters the shared session; renders the same InlineFieldInput as the body; computed/readonly/system highlights expose no editor; compact-layout expand-on-edit). RecordDetailView (app-shell) hosts ONE InlineEditProvider spanning record:highlights + record:details plus the single record-level InlineEditSaveBar; record:details drops its P1-local provider/bar and consumes the shared context; record:highlights threads the DataSource through. Guardrails preserved: computed/readonly/system non-editable, canEdit gate, OCC ifMatch + ConcurrentUpdateDialog, only-edited-keys. Verified: 205 tests (22 files) incl. a new HeaderHighlight editable suite; plugin-detail type-check + vite build clean; app-shell type-check clean w.r.t. this change. Browser-verified in real Chromium (harness mirroring the app-shell provider structure): double-click a highlight → whole record enters edit → a highlight edit + a body edit commit together in exactly ONE update({budget,health},{ifMatch:v1}); computed highlight exposes no editor. Completes #2407 across step 0 (#2529) + P1 (#2542) + P2.
This was referenced Jul 16, 2026
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 freeto 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.
What & why
P1 of #2407 (follow-up to #2402; builds on the merged step-0 refactor #2529). Lifts the inline-edit session out of
DetailView's private state into a record-level shared context, so a record page's surfaces share ONE draft and commit it in ONE atomic, cross-field-validated write — replacing the old per-field save loop. This isolates the riskiest plumbing; editable highlights are P2.Per the issue's own risk-isolation intent, this PR keeps the provider hosted by
record:details/RecordDetailDrawerand leaves app-shell untouched (the legacyDetailViewpath inRecordDetailViewnever wired inline edit). P2 lifts the provider to the app-shell page host so the highlights strip can share the same draft.Changes
InlineEditContext(@object-ui/react, new)InlineEditProvider+useInlineEdit. Pure UI state:editing,canEdit,draft,autoFocusField,saving,error+enter/setField/cancel/reset. A separate context fromRecordContext(mirrorsHighlightFieldsContext) so per-keystroke draft churn doesn't re-render otherrecord:*consumers.<InlineEditSaveBar>(plugin-detail, new)update(obj, id, draft, { ifMatch: data.updated_at })→refresh(); a409 CONCURRENT_UPDATEreuses<ConcurrentUpdateDialog>(reload / overwrite). Callback mode: loops the drawer's per-fieldonFieldSave.DetailViewuseInlineEdit()instead of owning inline-edit state; header/inline Save-Cancel bars + per-field batch-save removed (onFieldSaveprop dropped); approval-lock badge retained. No provider → read-only.record:detailsDetailViewconsumes the shared context; its per-field save + own OCC dialog are replaced by<InlineEditProvider>(canEdit = object-lifecycle gate) +<InlineEditSaveBar>.RecordDetailDrawerDetailViewin a local<InlineEditProvider>+<InlineEditSaveBar>(callback mode), preserving the plugin-gantt/calendar/kanban per-field contract.draftholds only user-edited keys → computed / read-only / untouched fields are never sent.Guardrails preserved
Computed (
formula/summary/rollup/auto_number) +readonly+ system fields expose no editor;canEditobject-lifecycle / permission gate; OCC (ifMatch+ConcurrentUpdateDialog); atomic partial update never writes computed/read-only fields; FLS/permission field filtering unchanged.Verification
@object-ui/react+plugin-detailvite builds clean;plugin-detailtype-check clean.InlineEditContextsuite (enter/setField/cancel/canEditgate) +InlineEditSaveBarsuite (exactly oneupdatewith only edited fields +ifMatch;409 → ConcurrentUpdateDialog; callback-mode per-field loop; Cancel discards without writing) + updatedrecord:details/RecordDetailDrawersuites + existingDetailSectioninline-edit suites.app-shelltype-check is clean with respect to this change (theonFieldSaveprop removal produced no errors); the only remaining error is a pre-existing, unrelatedCannot find module '@objectstack/formula'inmetadata-admin/celAuthoring.ts(missing external dep in this sandbox; resolvable in CI). This PR does not touch app-shell.Acceptance criteria (P1)
InlineEditContext/InlineEditProvider/useInlineEditexported from@object-ui/react.updatewith only edited fields; 409 →ConcurrentUpdateDialog.DetailView(drawer) still edits + saves.Next
P2 — editable highlights on the shared draft (using the
<InlineFieldInput>from #2529 + this context); lift theInlineEditProviderto the app-shell page host so highlights + details share one draft and one Save.🤖 Generated with Claude Code
Generated by Claude Code