PR: #497: feat(shared): EditableSurface edit-in-place primitive (TipTap) [Story-1994] by @ankitmaheshwari-com
View PR →
Story-1994

EditableSurface — the edit-in-place primitive (TipTap)

A new shared primitive at src/components/shared/editable-surface.tsx: plain text with no box chrome that edits in place. Hover shows the text I-beam, click drops the caret in (TipTap contenteditable), blur commits the text through an onSave callback. It is provenance-marker aware (canonical / counsel / proposed / locked) and gates by permission with readOnly. Foundation for the Matter Brief (EPIC-1990, pramaan-docs ADR-153): every text region of the Brief composes this surface.

What was built

The surface is the affordance

No Edit buttons, modals, or field chrome. TipTap (@tiptap/react 3.27 + StarterKit + Placeholder) renders the value as plain text; an empty region shows token-ink placeholder copy counsel clicks to type into. Enter commits a single-line surface, Escape reverts, blur saves only when the text actually changed.

Provenance-marker aware

canonical (ShieldCheck, never editable), counsel (pencil), locked (Lock, never editable) render a quiet glyph with the provenance string on hover via provenanceLabel. proposed carries a dotted underline, a Proposed tag, and the ✓ (Confirm & lock) / ✗ (Dismiss proposal) ratify affordance wired to onConfirm / onReject.

Permission gate

readOnly wins over everything: the region renders contenteditable="false" + aria-readonly, the I-beam cursor drops to default, and the ratify affordance stays hidden on a proposed value.

i18n + a11y + motion

All copy resolves through the EditableSurface namespace in both messages/en.json and messages/hi.json. The region announces itself (role="textbox", required ariaLabel, aria-multiline, aria-readonly; markers carry sr-only provenance text). The ratify cluster rises in and sinks out via AnimatePresence with the shared MICRO_TRANSITION token.

Relation to existing primitives

Storybook states (Shared/EditableSurface)

Tests and scope guards

Verification

pnpm lint
# passes with one pre-existing add-matter/avatar.tsx no-img-element warning

pnpm typecheck
# clean

pnpm test
# 157 files passed, 988 tests passed

pnpm storybook:check
# story-drift + Storybook production build passed

pnpm build
# Next.js production build passed

Files changed

9 file(s), +1030 / -0 lines

Path Role + -
messages/en.jsonConfig+8-0
messages/hi.jsonConfig+8-0
package.jsonConfig+5-0
pnpm-lock.yamlConfig+480-0
src/app/globals.cssApp route+10-0
src/components/shared/editable-surface-marker.tsxComponent+104-0
src/components/shared/editable-surface.stories.tsxComponent+170-0
src/components/shared/editable-surface.test.tsxComponent+97-0
src/components/shared/editable-surface.tsxComponent+148-0