From b16c131401d4ce15e0db9b4c8b7246022dfc512b Mon Sep 17 00:00:00 2001 From: DavertMik Date: Wed, 3 Jun 2026 10:56:21 +0300 Subject: [PATCH] feat: add compact (reading) view mode for Step block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a third Step view mode alongside vertical and horizontal. In compact mode each step renders as a tight, reading-focused row (number + content; no per-step header, no formatting toolbar, no Add Step / Step data / Expected result buttons). Focusing an editable field expands that step to the full editing layout and collapses it on blur with no layout jump — the OverType editors stay mounted so the caret is preserved. - Cycle the per-step view toggle through vertical -> horizontal -> compact. - Drop OverType's 4rem min-height floor in compact so reading rows hug a single line (live options.minHeight + textarea rows=1, plus higher-specificity CSS to beat OverType's runtime-injected min-height: 60px). - Prefix the expected-result line with a small inline "Expected" label while reading (preview pseudo-element, never serialized). - Fix Markdown preview not updating: the deferred BlockNoteView mount broke the useEditorChange subscription; render the view immediately (keeping the EditorErrorBoundary for crash recovery). Co-Authored-By: Claude Opus 4.8 --- src/App.tsx | 56 +++++++++++++--- src/editor/blocks/step.tsx | 114 ++++++++++++++++++++++++++------ src/editor/blocks/stepField.tsx | 46 ++++++++++++- src/editor/styles.css | 93 ++++++++++++++++++++++++++ 4 files changed, 276 insertions(+), 33 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index aacd1c6..053c979 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { Component, Fragment, useEffect, useMemo, useState, type ReactNode } from "react"; import { BlockNoteView } from "@blocknote/mantine"; import { useCreateBlockNote, @@ -382,6 +382,42 @@ function CustomSlashMenu() { ); } +const MAX_EDITOR_RETRIES = 3; + +// Recovers from BlockNote's transient render throws (e.g. getBlockFromPos +// resolving an undefined ProseMirror position while a node view mounts during a +// flushSync). The condition clears on the next frame, so we drop the subtree and +// remount it (capped) instead of crashing the whole app. +class EditorErrorBoundary extends Component< + { children: ReactNode }, + { hasError: boolean; nonce: number } +> { + private retries = 0; + private frame: number | null = null; + state = { hasError: false, nonce: 0 }; + + static getDerivedStateFromError() { + return { hasError: true }; + } + + componentDidCatch() { + if (this.retries >= MAX_EDITOR_RETRIES) return; + this.retries += 1; + this.frame = requestAnimationFrame(() => + this.setState((s) => ({ hasError: false, nonce: s.nonce + 1 })) + ); + } + + componentWillUnmount() { + if (this.frame) cancelAnimationFrame(this.frame); + } + + render() { + if (this.state.hasError) return null; + return {this.props.children}; + } +} + function App() { const editor = useCreateBlockNote({ schema: customSchema, @@ -621,14 +657,16 @@ function App() {
- - - + + + + +