Component library modularization (v0.5.0) + RichTextEditor - #21
Merged
Conversation
Search+Switch capped the search input at a fixed 711.5px and disallowed wrapping, so the Modo Edición switch overflowed the bar on narrow viewports. Mirrors the anonimizador fix: the search input now shrinks to a 320px floor and grows up to the 711.5px Figma reference as a max-width, and the row wraps the switch onto a second line, right-aligned, when there isn't room.
Figma still documents a "Replace archive" status and a dismiss (X) control on every state, but desktop-app never used either — VTT hid the dismiss button with a CSS hack targeting aria-label='Descartar', and no consumer ever set status="replace". Removed both from the component (props, styles, story, Figma-state doc) so desktop-app no longer needs the hack. "Reemplazar" stays, scoped to the real failure case: status "error" only.
Formalizes the hand-rolled "Transcribiendo audio…" notice from desktop-app's Voz a Texto flow as size="compact": padding/gap/icon already matched the default size, only the radius (xs, 2px) and text style (subtitle.sm.strong, 14px/600) differ. No dedicated Figma frame exists for this size — spec verified pixel-for-pixel against the existing desktop-app implementation instead.
Adds size="sm|md|lg|full" to DialogContent (default "md"). No Dialog/Modal family exists in the Figma UI Library file, so breakpoints are lifted from desktop-app's real per-usage overrides instead: confirmations (~420px), forms (~520px), and the "¿Cómo funciona?" tutorial (~900-1024px via a minWidth:900px + maxW:5xl! hack). Every size stays viewport-bound (min(px, vw)) rather than a hard minWidth, which is what let the tutorial dialog overflow narrow windows. max-height (90vh) + internal scroll now apply to every size, not just the one that previously set it inline.
SidePanel had no intrinsic width (root was w:"full"), so every consumer had to impose one itself. Adds size="sm|md|lg" (default "lg"): "lg" (479px) matches the only width Figma documents for this component (node 40002322:53113, no size variants exist there); "sm" (360px) and "md" (400px) formalize desktop-app's own real widths instead — the fixed wrapper around this component in the Voz a Texto turn editor, and the fully-custom (not yet using this component) entities panel in Anonimizador, respectively. maxW:"full" lets it still shrink under a narrower ancestor instead of forcing overflow.
New purely-presentational component for Dataset, Anonimizador and Voz a Texto onboarding. No Figma frame documents it; it formalizes desktop-app's own DropArea instead, which is already byte-identical across all three flows (verified: only copy/icon/extensions differ per call site — visuals, dragging and layout were already 100% shared). Fills two real gaps DropArea never had: a disabled state (opacity: 0.5, following Search's prev/next button convention) and :focus-visible styling (outline + shadow, matching Button). dragging is self-managed via the same dragenter/dragleave counter DropArea used, but accepts a controlled override for Storybook. onDrop only ever hands back raw File[] — extension filtering, single/multiple selection and the hidden file input all stay in desktop-app.
- type="preview-loading": formalizes desktop-app's ad-hoc loadingPreview
spinner box shown while a dropped file is still being parsed.
- selectable prop: replaces desktop-app's withoutSelection CSS hack
(hiding the checkbox via `button[role='checkbox'] { display: none }`)
with an actual prop.
- size="lg" (367x426px): the single-document preview from the redesigned
"Anonimizador - Preview" screen (Figma node 40002579:88220) — product
moved Dataset/Anonimizador to one document at a time, always requiring
manual validation, instead of a batch grid. size="lg" never renders its
own filename/error caption (no Figma type covers this combination); the
screen pairs it with a separate ArchiveRow for that instead.
Also fixes a layout bug surfaced while verifying the new size against
Figma: the filename/error <p> elements had no margin reset, so with no
preflight they carried the browser's default UA margin on top of the
intended gap.
Icon + title + description + optional leading/trailing action slots. Formalizes the file-info row from the single-document preview screen (Figma node 40002579:88220 — icon, filename, "N pag. - X mb", trailing Trash) and doubles as the row shape Voz a Texto's file list already uses (icon/play button, name, duration, delete), so both consumers can share one implementation. Actions are passed as fully-built elements — this component only places them. Also fixes the same missing margin reset bug (see previous commit) in FileDropZone's title/description <p> elements, caught while verifying ArchiveRow's title/description spacing against Figma.
Sweeps the rest of the library for the same bug found and fixed in ArchiveRow/ArchiveView/FileDropZone: no preflight means any <p>/<h*> without an explicit margin:"0" keeps the browser's UA default margin on top of the component's intended gap. - CardTool: title (<h2>) + description (<p>), 4px gap. - SidePanel: card title, section headings, and the merge-conflict confirm dialog's title + description. - TranscriptBlock: transcript body — visible as accumulating extra whitespace between every turn in a scrolling transcript. - TextField: helper and error message text. Verified each visually in Storybook against its previous (broken) spacing.
No Figma frame documents this; it formalizes desktop-app's own HowItWorks/Card (used in every pipeline's "¿Cómo funciona?" modal) — same 200x130 image, numbered badge, and card chrome. Adds the responsiveness the plan calls for that the original never had: two columns by default, one column on narrow windows. No i18next — image, alt text, title and description all come from the consumer; the step number is derived from each step's position.
Composes TutorialGrid over DialogContent size="lg", so it inherits the viewport-bound width that replaced desktop-app's minWidth:900px + maxW:5xl! hack for this exact screen. Every pipeline (Dataset, Anonimizador, Voz a Texto) can share this one dialog — only title, steps and trigger differ per consumer.
AymurAI UI Library "bar" node 40002579:88245 (bottom bar of the "Anonimizador - Preview" screen) — border-top, bg.secondary, 48px/24px padding, leading/actions slots via justify-content:space-between. leading and actions are pre-built content (desktop-app keeps BuiltBy, since DataGénero branding belongs to the product, not the library). Adds flexWrap + ml:"auto" on the actions slot so it stays right-aligned even when it wraps onto its own line on narrow windows — same pattern already used in Toolbar. mt:"auto" pushes the footer to the bottom when it's the last child of a flex-column page wrapper (the standard sticky-footer pattern); the library can't force this without owning the whole page's layout.
AymurAI UI Library "Menu" node 40000732:79289 (the header's apps dropdown) — until now fully desktop-side and inconsistent: the home grid used CardTool (4px card / 14px icon-box radius) while the header dropdown used a bespoke Card+local-FeatureIcon composition (16px/16px). Neither matched Figma, which specs 8px uniformly for both. FeaturesMenuItem: icon + label only (no description — a distinct, smaller card than CardTool), disabled styling follows Card's convention for the outer card and CardTool's for the icon box. `fullWidth` spans both columns for a trailing item, e.g. desktop-app's "Configuración" row — no Figma reference for that one, kept anyway since desktop needs it; the item is generic enough to not care. FeaturesMenu: just the grid chrome (bg, padding, radius, a new "menu" shadow token pixel-matched to Figma) — children are individual FeaturesMenuItems, wired up by the consumer (onClick/router navigation), matching the CardTool convention of leaving navigation to desktop. Verified it drops into a real Popover without doubling up the Popover's own chrome (override via className/style, same technique as elsewhere in this library). Left out of scope per product: the mocked-up "PDF a Word" item — not a real feature yet.
border.secondary was #EDF2F7 (near-white), but Figma's border/secondary variable is #9F99A5. This is the border applied to TextField's typed state, Select's dropdown panel, and Search's field/suggestion states — so a field with a value rendered an almost-invisible border instead of the intended medium gray. Search had already worked around it with a hardcoded [1px_solid_#9F99A5] escape; fold that back onto the corrected token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hasis The Select was controlled purely on the `value` prop with no internal state, so consumers that drive it through a ref/register pattern (the dataset validation forms) could never change the selection — picking an option reverted immediately. Manage the selection internally (seeded from `value`, synced via effect, exposed through the ref) so it works both controlled and ref-driven. Also add an opt-out `clearable` control (XCircle, highlights on hover to match the entity manager) and darken the label to text.default once a value is present, per Figma. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rd no-op highlight clicks Empty-runs paragraphs (list-exit, all-text-deleted) now render a <br> fallback instead of a zero-height <p>, so contentEditable can host a caret there and native typing lands in the right paragraph. Highlight swatch clicks with no active selection no longer flip the swatch's active-border state when applyMark is a no-op.
Adds AppFooter, Avatar, AvatarPill, ArchiveRow, CardTool, CategoryItem, FeaturesMenu, FeaturesMenuItem, FileDropZone, Option, PageTitle, Player, RichTextEditor, SidePanel, TranscriptBlock, TutorialDialog, TutorialGrid, and WorkflowStepLayout to the showcase page.
Three leftover phosphor-react references the library-wide migration missed: - SidePanel.tsx: the vendored ArrowsMerge glyph (needed because phosphor-react@1.4.1 predated this icon) is now available directly from @phosphor-icons/react, so ArrowsMergeIcon.tsx is no longer needed. - vite.config.ts: the Rollup external list still named the old package, which would have bundled @phosphor-icons/react into the library output instead of leaving it to the consumer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eEditor/EditorContent Swaps RichTextEditor's hand-rolled Selection-API editing engine for Tiptap's useEditor/EditorContent, and changes RichTextEditorProps' document/onChange from the custom RichTextDocument model to Tiptap's JSONContent. Toolbar buttons (bold/italic/underline/highlight/copy) are temporarily wired to no-ops with TODO(Task 4/5/6) markers; the old selection.ts/reconcile.ts modules and their tests are removed. RichTextEditor.stories.tsx and Showcase.stories.tsx are updated to pass JSONContent literals so the interface change type-checks. Also pins the @tiptap/* dependency versions (added in the previous task) to a mutually-compatible 3.28.0 set — @tiptap/core was previously ^3.29.0 while @tiptap/react peers on an exact 3.28.0, which caused pnpm to install two separate @tiptap/core copies and broke useEditor's TS overloads with cross-instance type mismatches. Also guards the editable-sync effect with an equality check: Editor#setEditable unconditionally emits a synthetic "update" event, so calling it unconditionally on every mount/readOnly-change (as a literal reading of the plan's snippet would) fired a spurious onChange with unchanged content.
… sync setContent defaults emitUpdate to true, so the effect that syncs an externally-changed `document` prop into Tiptap was firing onChange back with the very content it was just given — a controlled-component update-loop risk, and inconsistent with the already-guarded editable-sync effect in the same file. Pass emitUpdate: false and add a regression test proving an external document change never triggers onChange.
Toolbar Bold/Italic/Underline buttons now call editor.chain().focus() .toggleBold/toggleItalic/toggleUnderline().run() and reflect live active state via editor.isActive(...), forced to re-render on Tiptap's selectionUpdate/transaction events. Highlight and Copy remain no-ops (Tasks 5/6).
…t extension
Wires the 12 highlight swatches to editor.chain().focus().setHighlight()/
unsetHighlight(), with active-swatch border state driven by
editor.isActive("highlight", { color }). No-op if there's no active
selection. Also fixes two highlight tests that set a DOM Range/Selection
without first focusing the contentEditable root, which ProseMirror's view
requires in order to pick up the selectionchange event in jsdom.
Copy button remains a no-op TODO for Task 6.
StarterKit's bundled list extensions work fully out of the box; no configuration or custom keydown logic needed. Adds jsdom layout-API stubs required for reliable userEvent typing simulation into a Tiptap-backed contentEditable.
…d-rolled parser documentFromMarkdown() runs markdown through a headless Editor with the same StarterKit(link:false)+Highlight(multicolor)+@tiptap/markdown schema RichTextEditor uses, so headings/lists/marks come out shaped identically to what live editing produces. The official @tiptap/markdown package (Markdown extension, contentType: "markdown") satisfied every brief test case, including malformed-emphasis and accented-Spanish-text handling, so no fallback to marked+sanitized-HTML was needed. Also updates the FromMarkdown story to derive its sample doc from real markdown via the new function instead of a hand-authored JSONContent literal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BREAKING CHANGE: RichTextEditor now uses Tiptap JSONContent instead of the custom RichTextDocument model. See CHANGELOG.md for the full list of removed/renamed exports.
It was a public export via src/utils/rich-text/model.ts (re-exported from src/index.ts) and is now only an unexported internal helper in tiptap-text.ts. Verified unused by desktop-app. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s no longer load-bearing under Tiptap The final whole-branch review flagged that EditableRecoveryBoundary's continued necessity (it defends against a React "removeChild" crash from native contentEditable DOM mutation desyncing React's fiber) was never actually verified against the new Tiptap/ProseMirror engine. Added stress tests, including one that directly rips DOM nodes out of a live .ProseMirror element and forces a React re-render. No crash reproduces: @tiptap/react's EditorContent renders an empty <div> with no `children` prop, so ProseMirror owns and re-diffs the entire editor subtree itself, outside React's reconciliation, meaning there's nothing for a native mutation to desync from a React fiber tree anymore. Kept the boundary in place (removal was not requested) as defense-in-depth, with a comment documenting the empirical finding. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The option-hover tooltip and single-highlight behavior existed in desktop-app's local select.tsx primitive but were dropped when it was ported into this shared package. SelectOption now carries an optional description again, wrapped item-by-item in a delayed-hover Tooltip, and the checked item no longer competes with the hovered item for the same highlight background. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Required by the rich-text editor's upcoming table support (RichTextEditor's markdown parser and live schema both need TableKit registered). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…embedded variant - Register TableKit in both the live editor and the markdown-to-JSONContent parser, so tables round-trip through the same schema headings/lists already do. - Fix multicolor highlight: Tiptap writes attrs.color straight into an inline style, so a `category.*` token path was invalid CSS and every highlight silently fell back to the UA default. Swatches and stored marks now use real hex values instead. - Add heading/blockquote/hr/table typography rules that Panda's preflight was stripping (headings reset to inherit, lists to `list-style: none`), so markdown-parsed content renders with visible sizes/markers. - Add `variant="embedded"` — renders just the formatted content with no toolbar/title/card chrome, for consumers that supply their own frame (e.g. the Resumen finish screen's read-only preview) instead of building a second renderer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resumen shipped as a real feature, so its FeaturesMenuItem in stories/showcase no longer needs `disabled`; also switches to the *Icon-suffixed phosphor imports the rest of the story files already use. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every bare icon import (Copy, TextB, Question, CheckCircle, File, ...) is deprecated in favor of its *Icon-suffixed canonical export. Renames imports and JSX/prop usages across all affected components and stories; no behavior change.
…lidate Adds a small mustExist(value, description) helper and replaces every `!` non-null assertion with it, so a failed lookup throws a descriptive error instead of relying on a compile-only assertion. Fixes all 20 lint/style/noNonNullAssertion warnings pnpm validate was reporting; no test behavior change.
TS 5.9 flags baseUrl as deprecated (removed in TS 7). With moduleResolution "bundler", the "@/*" path mapping already resolves relative to this config file without it.
The Tiptap migration isn't a breaking change to a shipped release — it's still work in progress. Reverts the version to 0.5.0 and removes the CHANGELOG entry that framed it as a breaking 1.0.0 release.
…buttons, fix list/table plain-text export - Add a right-click context menu for tables: add/delete row, add/delete column, delete table — wired to @tiptap/extension-table's existing commands. New @radix-ui/react-context-menu dependency, wrapped as a ContextMenu primitive following the same pattern as Popover/Dialog. Only active in editable mode and while the caret is inside a table; otherwise the native browser context menu shows through. - Add Bullet List / Ordered List toolbar buttons next to Bold/Italic/ Underline, using StarterKit's already-registered toggleBulletList/ toggleOrderedList commands. - Fix serializeDocumentToPlainText: it only ever read a top-level block's *direct* text children, so bulletList/orderedList/table nodes (whose children are listItem/tableRow wrappers, not text) silently collapsed to an empty string instead of vanishing gracefully or rendering. Now recurses through list wrappers (each item becomes its own line) and renders tables as a lightweight markdown-style block (header row, `---` separator, pipe-separated body rows). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @jansaldo, your pull request is larger than the review limit of 150000 diff characters
Reviewer's GuideModularizes the component library for v0.5.0 (new layout primitives, size variants, icon migration, and archive/workflow refinements) and introduces a fully tested, Tiptap-based RichTextEditor with Markdown import, plain-text helpers, and Storybook/showcase integrations. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
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.
Summary
Two independent bodies of work landed on this branch since
v0.4.3:1. Component library modularization (v0.5.0)
AppFooter,ArchiveRow,CardTool,FeaturesMenu/FeaturesMenuItem,FileDropZone,TutorialGrid/TutorialDialog,WorkflowStepLayout,ContextMenuDialog(sm/md/lg/full),SidePanel(sm/md/lg),ArchiveView(sm/lg + loading state + configurable selection)Toolbarwraps responsively (anonymizer/search-switch),AppHeaderstepper stays vertically centered regardless of logo widthmargin: "0") on bare<p>/<h*>— this package has no Pandapreflight, so UA default margins were leaking throughphosphor-react→Icon-suffixed@phosphor-icons/reactexports (verified zero remainingphosphor-reactimports)2. New
RichTextEditorcomponent@tiptap/react(useEditor/EditorContent), with a toolbar for bold/italic/underline, multicolor highlight, native lists (autoformat + continue-on-Enter), and table editing (context menu, resizing off)documentFromMarkdown) for LLM-summary ingestion, and JSONContent-based plain-text helpers (tiptap-text.ts)Breaking changes for
desktop-appVerified against the current
desktop-appcheckout — no call sites affected:ArchiveProgressdrops the"replace"status and dismiss action (unused indesktop-app)Dialog's default content width is now capped bysize="md"(520px) instead of the old unsized 700px max (everydesktop-appDialogContentalready passes an explicitsize)SidePanelnow defaults to a fixed 479px width instead of filling its parent (everydesktop-appSidePanelalready passes an explicitsize)Test plan
pnpm vitest run— 58/58 tests passing across 5 filespnpm typecheckcleanpnpm lint(biome) cleandesktop-appcheckout forstatus="replace", bareonDismissonArchiveProgress, andDialog/SidePanelusages missing an explicitsize— none foundStarterKit/Highlight/TableKit) out ofRichTextEditor.tsxandmarkdown.tsto remove drift riskSummary by Sourcery
Release v0.5.0 of the shared UI library with a redesigned component showcase, new workflow/layout primitives, and a RichTextEditor built on Tiptap, while finishing the icon migration and tightening visual/layout consistency across components.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests:
Chores: