Repository navigation
Record-level inline edit: editable highlights + one atomic Save (follow-up to #2402) #2407
Description
Activity
Execution note (sequencing): land the
<InlineFieldInput>extraction as a standalone, behavior-preserving refactor PR first. It's already covered by the #2402DetailSectioninline-edit tests, so it can go in green with zero UX change, and it shrinks P2's risk surface — P2 then only adds the editable-highlights wiring on top of an already-proven shared input component.Suggested order:
- (0)
<InlineFieldInput>extraction — no-op refactor. - (P1) record-level
InlineEditContext+ one atomic OCC save (DetailView delegates; standalone wrapped). Highlights still read-only. - (P2) editable highlights on top.
- (0)
✅ Verified end-to-end — closing
All phases merged: step 0 (#2529), P1 (#2542), P2 (#2549), plus the
reference-key follow-up fix (#2587).Live browser verification (isolated stack: fresh showcase backend
objectstack dev --seed-admin --fresh+ console dev from objectui main, showcase Project "Website Relaunch"):P2 + #2587 fix
- Double-click on the 客户/account highlight enters the shared record edit session; the lookup editor hydrates the current value (Northwind chip) and the picker lists candidates (Northwind 已选 / Contoso / Fabrikam / Initech / Globex). Before fix(detail): highlight strip lookup editor honors ObjectStack
referencekey (#2407) #2587 this showed the "选择..." placeholder and "未找到选项" because the backend serves"reference": "showcase_account"(notreference_to) and HeaderHighlight didn't normalize the key. - Edited the account highlight (→ Contoso) AND a body field (Financials 已花费 60000 → 61000) in one session → one Save issued exactly one
PATCH /api/v1/data/showcase_project/:id(200) carrying both fields; both values persisted across a full page reload (highlight strip resolves the new label correctly); activity feed logged a single update. - Cancel restores: re-entered edit (Contoso chip hydrated — fix works for the new value too), changed 预算 to 999000, hit 取消 → value back to 150,000, no PATCH fired.
- Compact-layout editors legible: select/lookup/date/number editors render at highlight width with expand-on-edit.
P1 (verified by merged unit/e2e suites in #2542 + exercised above): shared
InlineEditContextsession, one atomic record-level save bar, draft holds only edited keys.Guardrails observed live: computed/system fields exposed no editor;
affordances.editgate honored.Unit coverage:
packages/plugin-detailsuite (23 files / 211 tests) green, including the new reference-key normalization regression tests from #2587.- Double-click on the 客户/account highlight enters the shared record edit session; the lookup editor hydrates the current value (Northwind chip) and the picker lists candidates (Northwind 已选 / Contoso / Fabrikam / Initech / Globex). Before fix(detail): highlight strip lookup editor honors ObjectStack
Follow-up to #2402 (Edit-as-primary CTA + double-click inline edit, merged). Delivered as one issue, two phases.
Problem
Goal
Lift inline edit into a record-level shared mode so the highlights strip and the details body share ONE edit session + ONE atomic, cross-field-validated Save. Fixes (2), honors (1). Long-term: inline edit becomes a reusable record-level primitive (any `record:*` block can opt in), not `DetailView`-private state.
Design
New primitives
Wiring
Standalone `DetailView` (drawer / direct usage, e.g. `RecordDetailDrawer`): DetailView always consumes `InlineEditContext`; standalone hosts get a local `` + `` wrapper (no dual-mode branching inside DetailView). This is the main regression surface.
Phasing
Each phase is its own PR.
Guardrails preserved
Computed (`formula`/`summary`/`rollup`/`auto_number`) + `readonly` non-editable; `affordances.edit` object-lifecycle gate (via `canEdit`); approval lock; OCC (`ifMatch` + `ConcurrentUpdateDialog`); atomic partial update never writes computed/read-only fields; FLS/permission field filtering unchanged.
Cost & risk
Acceptance criteria
P1
P2
Verification
Reuse (don't reinvent)
`HighlightFieldsContext` pattern (RecordContext.tsx); `ConcurrentUpdateDialog` + `isConcurrentUpdateError`; sticky-bar chrome from `DraftPreviewBar.tsx` / `RuntimeDraftBar.tsx`; the update/updateOne/patch + `ifMatch` fallback in `record-details.tsx`.