4.1.0: theme tokens, modern restyle, FastCommentsLiveChat widget, content-HTML fixes, web test lane#14
4.1.0: theme tokens, modern restyle, FastCommentsLiveChat widget, content-HTML fixes, web test lane#14winrid wants to merge 10 commits into
Conversation
…xes, web test lane Theming and visual redesign: - New FastCommentsTheme semantic token layer (colors, spacing, radii, font sizes, font weights, avatar sizes; names mirror the Android SDK). New optional `theme` prop on all widgets generates the entire default style tree from tokens; getLightTheme/getDarkTheme/resolveTheme exported. `styles` together with `theme` merges on top (styles win); `styles` alone keeps the legacy full-replace behavior so existing integrations and skins are unaffected. Dark and erebus skins now derive from the dark theme; setupDarkModeSkin is deprecated in favor of the theme prop. - Redesigned "modern neutral" default look: hairline separators, pill vote buttons and chips, filled primary action buttons, 40px shadow-free round avatars, a consistent 14px+ type scale, themed link colors wired through react-native-render-html tagsStyles. De-hardcoded the editor toolbar and mention popup into new style keys (commentTextArea.toolbar*, mentionPopup.itemSelected, comment.textLinkStyles, liveChat.*). New FastCommentsLiveChat widget (Android LiveChatView parity): - Chat preset over FastCommentsLiveCommenting: chronological messages with the composer below the list, live header strip (connection dot + user count), auto-scroll to new messages that pauses while scrolled up and resumes at the bottom or on your own message, older history loaded on scroll-to-top, votes and reply threading disabled. All presets overridable via config. config.maxReplyDepth: 0 now hides reply buttons. Bug fixes: - Feed posts composed in the SDK rendered with literal <p> tags: the composer wrapped content in <p>, which the server entity-escapes (not an allowed tag). Send <br>-separated content like the web feed widget. - Multi-paragraph comments lost their line breaks: the editor emits <p>-wrapped paragraphs, which the server strips, gluing paragraphs together. Editor HTML is now normalized via editorHtmlToServerHtml() before comment create and edit. Test framework credibility work: - The jest mock for react-native-enriched now mirrors the real editor's HTML contract (each line arrives as a <p> block), so the normalization path is actually exercised. - New web test lane (npm run test-web): vitest + jsdom in example-web, mounting the SDK through react-native-web with the real tiptap editor against the real backend. Covers the .web.tsx / react-native-web class of regression the node suite cannot execute. - New E2E specs: live-chat-widget (dual-session two-way exchange, header strip, presets, auto-scroll, load-older), theme (token flow, precedence, skin back-compat), guest submit (verified red with the old bug re-introduced), multi-paragraph comments. tests-ui/README.md documents what each remaining mock does NOT cover. - New theme-styles unit tests lock in token flow and the typography rules (no sub-base font sizes, no text opacity, no uppercase transforms). Also migrates to fastcomments-sdk 3.2.4 (renamed logout/getTrending/ search/getLarge methods; restored getCommentsForUser and the react urlId param) and adds dom to tsconfig lib for the web code paths.
Functional fixes:
- Guest voting dead end: a stale anon session (no username/email) was
treated as authenticated, so the vote 401'd silently with no prompt and
no feedback. New user-auth-state helpers gate the vote auth form, a
rejected session vote falls back to collecting identity, other failures
render a visible themed error, and the vote auth inputs gained testIDs
and proper input styling.
- Ghost anon session chrome: the top bar rendered an empty username box,
a Log Out menu, and a notification bell that 401'd into an infinite
spinner for users who never logged in. The top bar requires an
identified user; the notification list catches load failures and shows
an error; the bell badge hides at zero.
- Reply indentation: depth was attached to the list's prop copy but the
store object won the merge, so every thread rendered flat. Depth now
comes from the prop; default indent raised; regression assertion added
to the threading spec (red before the fix).
- Web dialogs: Alert.alert is a no-op under react-native-web, making
reply-cancel, delete/block confirms, and error display dead ends in
browsers. All dialogs route through a new platform shim
(window.confirm/alert on web, Alert.alert on native).
- Editor theming: typed text was black-on-near-black in dark mode and
there was no placeholder. The editor now receives themed color, font
size, placeholder text/color, and no longer autofocuses on mount
(which also stole focus from host pages). Covered by a web-lane test.
- Dark icon variants: hasDarkBackground is now derived from the resolved
theme's background luminance, so theme={getDarkTheme()} gets white
bell/vote/menu icons without setting a second config flag.
- Inline formatting: b/strong/i/em/u/s/code lost their default styles on
web and posted bold/italic rendered as plain text; explicit tag styles
added to comment.textLinkStyles (unit-tested).
- Chat: opens pinned to the newest message (onContentSizeChange; the
mount effect fired before web layout settled) and no longer renders
the comment-count/sort header, which contradicted chronological order.
- Comment length: the editor silently truncated at the character limit,
losing everything past it with no warning; the SDK now blocks submit
with the translated COMMENT_TOO_BIG error instead.
UX polish:
- Identity first: username renders above the "Unverified comment" label.
- Progressive disclosure: the guest name/email form appears on composer
focus (or submit) instead of rendering as a permanent registration
form. Guest spec updated to drive focus explicitly.
- Touch targets raised toward 44px (vote pills 36px+ with 16px icons,
reply/sort/bell/three-dot padding); vote counts show 0 with a muted
zero style instead of hiding; modal menus gained a scrim; the composer
and list share one 16px gutter; BackHandler no longer warns on web.
|
Added a second commit fixing the usability defects surfaced by a UX + QA review of the web example: Functional: guest vote dead end (silent 401, no auth prompt), ghost anon-session chrome (empty username box, Log Out for guests, bell spinning forever on 401), flat reply threads (depth lost in a store merge), Alert.alert no-ops on web (reply cancel, delete/block confirms, error display), black-on-black editor text in dark mode + missing placeholder + mount focus steal, light icons on dark backgrounds (hasDarkBackground now derived from the theme), bold/italic rendering as plain text on web, chat opening at the oldest message, and silent comment truncation at the character limit (now a visible COMMENT_TOO_BIG error). Polish: username above the "Unverified comment" label, progressively disclosed guest form, 36px+ touch targets, visible zero vote counts, menu scrim, aligned gutters, no bell badge at zero, no BackHandler warning on web. All lanes green after the changes: 21/21 E2E suites (58 tests), 134 unit tests, 4 web-lane tests, tsc clean. Verified visually in the browser: light, dark, and the chat widget on a 390px viewport. |
Every spec already authenticates with Secure SSO tokens implicitly; this
adds explicit assertions for the SSO-specific behaviors:
- Secure SSO: signed user lands in the top bar, no guest form on focus,
comments attributed to the SSO identity.
- Simple SSO: an unsigned { username, email } user is recognized as the
session identity and can comment.
- Login gate: with an SSO config but no user, the composer is replaced
by a login button that fires the host loginCallback.
- Logout: the Log Out menu item fires the host logoutCallback.
Adds testIDs to the SSO login button and the modal-menu open button.
…mit, mention groups Closes the feature gaps customers hit when migrating from the iframe-based react wrapper (config surface: pageReactConfig, voteStyle, mentionGroupIds, plus an arrow-in-the-box submit layout): - Page reacts (config.pageReactConfig): new PageReactsBar above the composer renders each configured react as an image button (src / selectedSrc) with a count; tapping toggles the react optimistically and persists via the page-reacts API; with showUsers, tapping the count reveals who reacted. New src/services/page-reacts.ts wraps the new typed endpoints (GetPageReacts / AddPageReact / DeletePageReact / GetPageReactUsers, added to the fastcomments-sdk spec). New pageReacts styles section. - voteStyle: VoteStyle.Heart renders a single like toggle per comment (no up/down pair): like = vote up, tap again to remove. Uses new ICON_HEART / ICON_HEART_ACTIVE image assets (appended to the asset enum to keep existing numeric values stable), overridable through the assets prop, e.g. with a star. - config.useInlineSubmitButton anchors an icon-only send button inside the comment box and suppresses the standalone labeled button. - config.mentionGroupIds is forwarded to the mention user search. Covered by tests-ui/specs/idcolab-parity.test.tsx (heart toggle round trip, page react toggle + persistence across reload + count, inline submit posting and clearing the editor), written red before each implementation. Full suite green: 23 E2E suites / 64 tests, 134 unit tests, 4 web-lane tests.
The submit guard checked the raw editor value, but the editor (tiptap web and native) reports an empty box as '<p><br></p>' or '<p></p>', never '' - so tapping send on an empty composer POSTed and the server stored a blank "<br />" comment. New isEditorHtmlEffectivelyEmpty() strips paragraph wrappers, <br>s, and whitespace before deciding (image-only content still counts as non-empty); the submit guard and the missing-identity check both use it. Caught by a new E2E spec driving the real empty-editor shapes (red before the fix) plus unit coverage; verified in the browser that no request fires on empty submit.
countAboveToggle (web widget parity): - Widgets with useShowCommentsToggle now start collapsed like the web widget, keeping the full scaffold (composer, page reacts, toggle) instead of replacing everything with a bare toggle button. - New config.countAboveToggle renders the first N root comments as a teaser above the Show Comments toggle while collapsed (replies excluded); the toggle reveals the rest. Tapping a teaser's replies toggle expands the whole list, matching the web widget. The legacy hideCommentsUnderCountTextFormat mode keeps its bare-link behavior. - Collapsed state suppresses the empty-state message and pagination. Default media buttons (web widget parity): - The GIF toolbar button now renders without any host callback and opens the SDK's built-in GifBrowser; the selection inserts into the comment. Hosts can still take over via pickGIF. The browser is wrapped in a Modal: rendered inline it sat underneath later comment rows on web (virtualized-list stacking, same class as the mention popup). - The image button renders by default on web using a DOM file-input fallback feeding the existing upload path; on native it appears once the host supplies pickImage (the SDK ships no native file picker). - GifBrowser cleanup: scrim, sized/centered modal, images contained to the modal width (previously sized to the window and overflowing), rounded corners, testIDs, no BackHandler warning on web. Tests (red first): countAboveToggle collapse/teaser/expand round trip in idcolab-parity, new editor-toolbar-media spec (default GIF button -> built-in browser -> trending from the real backend -> selection lands in the editor; image button + http-URL insert), and a web-lane assertion that both buttons render with no callbacks. Full suite green: 24 E2E suites / 68 tests, 136 unit tests, 5 web-lane tests.
…ll-width modal The built-in GIF picker opened as a near-full-width centered modal, which on desktop was huge and visually disconnected from the button that opened it. It is now a 340px dropdown anchored directly under the toolbar GIF button, like the web widget's dropped-content panel: - On web the panel is portaled to document.body (the virtualized list clips/overpaints inline overlays) and positioned with page coordinates measured off the button, repositioning on scroll/resize and clamped to the viewport edge. On native it anchors below the toolbar inline. - Panel styling: bordered, rounded, shadowed, maxHeight 420 with internal scrolling and overflow clipped (content previously bled past the rounded border). New gifBrowser.popover style key carries the sizing. - gifBrowser.centeredView is no longer a full-screen absolute overlay; the example app that mounts GifBrowser standalone now provides its own overlay wrapper. Verified on a 1440px viewport: panel sits 4px below the button at the button's x, selection still inserts and closes. Suite green (the single live-vote timeout in the full run reproduces green in isolation and is unrelated WS flake): 24 E2E suites, 136 unit tests, 5 web-lane tests.
Typing in the GIF picker now searches automatically: each keystroke re-arms a 700ms trailing debounce (the web extension's delay), the final term searches without pressing Enter, clearing the box falls back to trending, and identical terms are deduped. Submit/Enter still searches immediately. Fixes two latent bugs in the old handler: it sent the PREVIOUS keystroke's text (stale state read), and it reused the pagination page from infinite scroll so a new term could start mid-pagination; new searches now always start at page 0. Covered by a new E2E test (type with no submit -> first result changes via the real search API) plus a browser check confirming exactly one request fires while typing 15 characters. Full suite green: 24 E2E suites / 69 tests, 136 unit tests, 5 web-lane tests.
… toggle; snug page reacts
Web image/GIF attachment (the web editor cannot hold images at all:
react-native-enriched's web setImage is a no-op stub and its tiptap
schema has no image node, so inline insertion silently vanished):
- Picked GIFs / uploaded images on web attach as preview chips under the
editor (remove buttons, testIDs) and are appended to the comment as
[img]src[/img] tokens at read time - the wire format the web widget
itself submits. Editing on web extracts existing tokens back into
chips. Native keeps true inline insertion via setImage, converted to
tokens at submit (the server strips raw <img> from user submissions,
so even native inserts were silently dropped server-side before).
- The content is otherwise untouched: no <br> injection, no markup
rewriting. Block layout is a renderer concern, like the web widget's
`.inline-image { display: block }` CSS.
Render-side (operates on commentHTML, the field we render):
- A domVisitor retags the server's <a class="inline-image"> wrapper to a
block custom element, so uploaded images render on their own line on
every platform; a custom renderer preserves tap-to-open. 10px vertical
margins and left alignment ship via the existing classesStyles channel
('inline-image' / 'comment-image').
UI polish from review:
- Show/hide comments toggle is now a quiet, compact centered text
control (2-3px padding, neutral colors) instead of a full-width blue
bar, matching the web widget's treatment.
- Page react counts hug their icon (2px gap); the shrunken buttons keep
a usable touch target via hitSlop.
Tests: media E2E asserts the STORED comment HTML contains the image
(the gap that hid the server-side strip), web-lane test drives the real
gif-pick -> attachment chip flow, normalizer units cover tokenization
with clean content. Suite green: 24 E2E suites / 69 tests, 137 unit
tests, 6 web-lane tests. Verified in the browser: clean [img] payload,
image on its own line at the text gutter with 10px margins.
…y-reply close - Buttons (Submit, Reply, pagination, banners) now use the web widget's off-black (#222) instead of blue; dark theme inverts to light-on-dark like the web widget's .dark button treatment. Content hyperlinks stay blue. Vote/like buttons lose their grey pill backgrounds (bare icons, same touch targets). - Vote auth form no longer rearranges the vote buttons: the options row is flex-start (space-between scattered it across the form's width) and the comment toolbar top-aligns so controls don't re-center against the expanded form. - Closing an EMPTY reply no longer asks to discard: the open composer registers a dirty check (store.replyDirtyCheck) the close flow consults; half-written replies still confirm. New E2E covers both directions; verified on web with real window.confirm dialogs. - The sort selector and the per-comment three-dot menu render as real dropdowns on web, anchored under their triggers (portaled to the body, scroll-tracked, outside-click close, selected sort highlighted) instead of centered modals; native keeps the modal pattern, and sub-flows like the edit form remain modals everywhere. The comment menu's trigger rect is captured at press time and threaded through OpenCommentMenuRequest. - Show/hide comments toggle is a quiet compact text control; page react counts hug their icons (touch targets preserved via hitSlop). Suite green: 24 E2E suites / 70 tests, 137 unit tests, 6 web-lane tests. Browser-verified: anchored sort + comment menus (geometry asserted), off-black reply text, vote layout stable with the auth form open.
Summary
Visual redesign + Android-SDK parity work, two real content-HTML bug fixes, and a test-framework credibility overhaul. Purely additive API; bumps to 4.1.0.
Theming
FastCommentsThemesemantic token layer (colors, spacing, radii, font sizes, font weights, avatar sizes; token names mirror the Android SDK'sFastCommentsTheme). The entire default style tree is generated from tokens.themeprop onFastCommentsLiveCommenting,FastCommentsFeed, andFastCommentsLiveChat;getLightTheme()/getDarkTheme()/resolveTheme()exported. Dark mode is one token set.stylestogether withthememerges on top (styles win);stylesalone keeps the legacy full-replace behavior. The dark/erebus skins now derive from the dark theme;setupDarkModeSkinis deprecated in favor of thethemeprop.Visual redesign ("modern neutral")
Hairline separators, pill vote buttons and chips, filled primary buttons, 40px shadow-free round avatars, consistent 14px+ type scale, themed link colors through render-html
tagsStyles. The editor toolbar and mention popup no longer hardcode colors; new style keys:commentTextArea.toolbar*,mentionPopup.itemSelected,comment.textLinkStyles,liveChat.*.New widget: FastCommentsLiveChat (Android LiveChatView parity)
Chronological messages, composer below the list, live header strip (connection dot + user count), auto-scroll to new messages (pauses while scrolled up, resumes at the bottom or on your own message), older history on scroll-to-top, votes and reply threading disabled. All presets overridable via
config.config.maxReplyDepth: 0now hides reply buttons.Bug fixes
<p>text: the composer wrapped content in<p>, which the server entity-escapes (not an allowed tag). Now sends<br>-separated content like the web feed widget. This was the root cause of the 5 feed E2E suites failing on main.<p>-wrapped paragraphs, which the server strips. Editor HTML is now normalized (editorHtmlToServerHtml) before comment create and edit.Test framework credibility
The suite previously stayed green while real flows were broken. Changes so that cannot recur for these classes:
react-native-enrichedjest mock now mirrors the real editor's HTML contract (<p>-wrapped paragraphs), so the normalization path is actually exercised.npm run test-web(vitest + jsdom inexample-web/): mounts the SDK through react-native-web with the REAL tiptap editor against the real backend; covers the.web.tsx/ react-native-web regression class the node suite cannot execute.tests-ui/README.mdnow documents exactly what each remaining mock does NOT cover.Dependency note
Migrates to fastcomments-sdk 3.2.4 (renamed
logout/getTrending/search/getLarge; restoredgetCommentsForUserand the reacturlIdparam that a spec regen dropped). The fastcomments-sdk-js working tree carries that regen + restore and needs its own commit/release; the durable fix is declaringurlIdon the tsoa react endpoint and porting/comments-for-userto a tsoa controller so regens stop dropping them.Testing
tsc --noEmitclean (main does not compile against the regenerated SDK).?theme=dark), and chat widget (?widget=chat) all render correctly.Not covered here: on-device visual pass for native iOS/Android and the erebus skin.