Skip to content

Composer editor: RichEditor growth, DockablePanel, collab cursors, Autocomplete density - #402

Open
horner wants to merge 11 commits into
mainfrom
feat/composer-editor-and-dockable-panel
Open

Composer editor: RichEditor growth, DockablePanel, collab cursors, Autocomplete density#402
horner wants to merge 11 commits into
mainfrom
feat/composer-editor-and-dockable-panel

Conversation

@horner

@horner horner commented Aug 25, 2026

Copy link
Copy Markdown
Member

What eCase's document composer needs from @mieweb/ui: RichEditor grown for composing (ED.25), DockablePanel — a dialog that collapses to a dock without unmounting (ED.26), the kerebron theme bridge + WASM load queue (ED.27), named collab cursors via CollabConfig.user (ED.38), collab reseed for rooms the join left empty, focus()-at-mount fix, assessment defaultAddMode, and the Autocomplete Input-density slot fix (same family as #388, which is merged in via main).

Dogfooded in mieweb/eCase, which pins this branch as a submodule.

horner added 11 commits August 22, 2026 22:36
When a picker trigger is rendered, DateInput builds its own markup instead of composing Input, and that markup carried no data-slot attributes. condensed-view.css keys density purely off data-slot, so a date field stayed at the comfortable h-10/text-base size next to condensed Input siblings. Tag the wrapper, label, input, error and helper with the shared Input slots, and give the calendar/clock trigger its own slot so its icon scales with the condensed field.
Adds the imperative getContent()/focus() handle (onChange lags the last keystroke, so submit must read the editor), disabled, id/aria-label/aria-labelledby, per-instance assetLoad, and reload-on-value-change for non-collab editors. The editor now mounts into a disposable child div because CoreEditor.destroy() replaces its host with a clone, and programmatic loads are guarded so they never echo back through onChange. The dark-mode observer duplicated in the mermaid plugin becomes the shared useIsDarkMode hook.
Full-screen modal that clips to a bottom-right strip instead of resizing or unmounting, so anything inside that measures itself (a rich-text toolbar's ResizeObserver, a chart) never rebuilds mid-edit. Modality follows the mode: docked drops aria-modal and un-inerts the app, because working behind it is the point. Escape collapses dirty work and closes clean; discarding always confirms; a dirty panel guards beforeunload. Layer with --mieweb-dockable-panel-z.
eSheet had three copies of the same kerebron -> mieweb theme CSS (the
document list composer, RichTextEditorField, KerebronNotesComposer). It
belongs next to the editor that needs it, so kerebron.css now owns the
--kb-color-* mapping, the custom-menu overrides and the overflow-item
touch target, and the consumers import it instead of shipping their own.

RichEditor also serialises its first loadDocumentText: tree-sitter's
createParser races when two editors load at once, which the composer hits
as soon as a docked panel and an inline field are alive together.
ExtensionYjs's position plugin skips awareness states that carry no
kerebron:user, so remote cursors never rendered. CollabConfig gains
user { name, color? }, threaded into HuddleYjsKit and published on the
provider's awareness, so a host that knows who it is gets named,
coloured cursors for free.

Design record: eCase editor-plan.md Phase 7 ED.38 (mieweb/eCase).
… from it

defaultColorMapper hashes user.id; a user without one threw
undefined.length on every remote decoration pass.
The Yjs binding is documented to seed an empty room from the local
content, but extension-yjs 0.8.x's sync import can land as an overwrite
that blanks the just-loaded seed — a revise-with-prefill opened an
empty editor and, through onChange, wiped the caller's draft state too.
After joining, the editor now probes until the join settles; an empty
document with a non-empty seed means nobody's content won, so it
reloads the seed and the binding pushes it into the room. Joiners pass
no value, so exactly one participant ever reseeds.

Also catches the collab-kit constructor test up with the ED.38 user
argument.
The 'What to add' selector always started in auto; a consumer whose
users mostly assess concerns (eCase) can now start it on 'problem'
(or 'order'). Default stays 'auto' — no behavior change elsewhere.
Hosts focus the editor as a compose panel opens, which can precede the
lazy kit load; focus now applies once setup resolves instead of
silently missing.
Same family as #388: condensed-view.css keys density off
data-slot='input', and the combobox input carried its own
'autocomplete-input' slot nothing consumed. Emit the shared slot.
Copilot AI lite review requested due to automatic review settings August 25, 2026 06:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the UI kit to support eCase’s document composer workflows by enhancing the Kerebron-backed RichEditor, adding a dockable “non-unmounting” panel overlay, and tightening theme/density integration across components.

Changes:

  • Upgrade RichEditor for composing: imperative handle (getContent, focus), disabled/read-only surface behavior, dark-mode tracking, collab cursor identity, and collab reseed healing.
  • Add DockablePanel, a full-screen modal that can collapse to a dock strip without unmounting/resizing its content.
  • Improve theming and density behavior: Kerebron CSS token bridge + menu overrides, shared useIsDarkMode hook, Autocomplete density slot fix, and Assessment defaultAddMode.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/styles/kerebron.css Bridges Kerebron palette variables to mieweb design tokens and forces menu colors to follow theme.
src/index.ts Exports the new DockablePanel component from the package entrypoint.
src/hooks/useIsDarkMode.ts Adds a shared DOM-root based dark-mode reader + reactive hook.
src/hooks/index.ts Re-exports useIsDarkMode/isDarkMode from the hooks barrel.
src/components/SuperChat/plugins/mermaid.tsx Switches Mermaid rendering to use the shared dark-mode hook (removes local duplicate).
src/components/RichEditor/RichEditor.tsx Major RichEditor enhancements: ref handle, loading queue, collab reseed, disabled mode, dark mode class toggling, safer mounting.
src/components/RichEditor/RichEditor.test.tsx Expands test coverage for new RichEditor behaviors and collab user wiring.
src/components/RichEditor/RichEditor.stories.tsx Adds a “composer” story showing label/disabled/save via getContent().
src/components/RichEditor/index.ts Exports the new RichEditorHandle type.
src/components/RichEditor/editorKits.ts Extends CollabConfig with user and forwards to Yjs kit.
src/components/RichEditor/collabKit.ts Publishes kerebron:user into awareness so remote named cursors render.
src/components/DockablePanel/index.ts Adds barrel export for the new DockablePanel module.
src/components/DockablePanel/DockablePanel.tsx Implements the dockable modal/docked overlay, inerting, focus handling, and announcements.
src/components/DockablePanel/DockablePanel.test.tsx Adds behavior tests for portaling, modality, focus, docking, and dirty confirmations.
src/components/DockablePanel/DockablePanel.stories.tsx Adds Storybook demos for dockable composer and modal-only usage.
src/components/Autocomplete/Autocomplete.tsx Changes combobox slot to the shared Input density slot (data-slot="input").
src/components/Autocomplete/Autocomplete.test.tsx Adds a test asserting the density slot is exposed.
src/components/Assessment/Assessment.tsx Adds defaultAddMode prop and uses it as the initial add-mode state.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +117 to +128
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent): void => {
if (event.key !== 'Escape') return;
if (dockable && dirtyRef.current && modeRef.current === 'full') {
onModeChangeRef.current?.('docked');
return;
}
requestClose();
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [dockable, requestClose]);
Comment on lines +1 to +7
import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants