Composer editor: RichEditor growth, DockablePanel, collab cursors, Autocomplete density - #402
Open
horner wants to merge 11 commits into
Open
Composer editor: RichEditor growth, DockablePanel, collab cursors, Autocomplete density#402horner wants to merge 11 commits into
horner wants to merge 11 commits into
Conversation
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.
Contributor
There was a problem hiding this comment.
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
RichEditorfor 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
useIsDarkModehook, Autocomplete density slot fix, andAssessmentdefaultAddMode.
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'; |
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.
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.