stacker.news icon indicating copy to clipboard operation
stacker.news copied to clipboard

Remove legacy text input/markdown handling

Open Soxasora opened this issue 4 weeks ago • 2 comments

Description

Based on #2657 Removes unused text input code and legacy markdown-related code.

Removed

  • LegacyText (ReactMarkdown) -> Text (SNReader)
    • Mention -> UserMentionNode
    • Sub -> TerritoryMentionNode
    • Item -> ItemMentionNode
    • Footnote -> Reference, Definition, Section, Backref nodes
    • MediaLink -> MediaNode
    • Table -> TableNode (handled by lexical)
    • CodeSkeleton
    • Code -> CodeNode (handled by lexical with Shiki)
    • P
  • remarkToc -> handled by MDAST and Lexical
  • remarkUnicode -> not needed under KaTeX
  • remarkMath -> extracted mdast-util-math
  • rehypeSN -> feature parity achieved via MDAST visitors and transforms
  • rehypeMathjax -> replaced with KaTeX
  • rehypeSNStyled -> superseded by MDAST
  • remarkGfm -> not needed
  • React Syntax Highlighter -> replaced with Shiki

Bundle analysis

tmi: Lexical post-removals is 0.03 MB heavier than master on first load (what matters for the client). I was expecting a much higher first-load and overall bundle from Lexical, considering all the things it handles and can handle, but it's instead marginal.

Master

_app image

+ First Load JS shared by all              1.01 MB
  ├ chunks/framework-c6ee5607585ef091.js   44.9 kB
  ├ chunks/main-ea7afcb22284b775.js        37.3 kB
  ├ chunks/pages/_app-99d52cc32cadfb73.js  883 kB
  ├ css/554058231c942e24.css               46.5 kB
  └ other shared chunks (total)            2.13 kB

Lexical pre-removals

_app image

+ First Load JS shared by all              1.09 MB
  ├ chunks/framework-c6ee5607585ef091.js   44.9 kB
  ├ chunks/main-ea7afcb22284b775.js        37.3 kB
  ├ chunks/pages/_app-e6dec95ecd8fe254.js  948 kB
  ├ css/198ebdadd5386d4b.css               55.4 kB
  └ other shared chunks (total)            6.2 kB

Lexical post-removals

_app image

+ First Load JS shared by all              1.04 MB
  ├ chunks/framework-c6ee5607585ef091.js   44.9 kB
  ├ chunks/main-ea7afcb22284b775.js        37.3 kB
  ├ chunks/pages/_app-db96a9243c75fba9.js  897 kB
  ├ css/087336682c6d686c.css               55.9 kB
  └ other shared chunks (total)            6.14 kB

Additional Context

The following removals have been postponed:

  • useDualAutocomplete
    • provides user and territory mentions to inputs, should be replaced with Lexical but this raises questions about avoidable overhead on simple text inputs
  • remove-markdown
    • removes markdown syntax from given text, its performance has to be evaluated to justify migration to our own MDAST system

Checklist

Are your changes backward compatible? Please answer below:

For example, a change is not backward compatible if you removed a GraphQL field or dropped a database column.

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:

For frontend changes: Tested on mobile, light and dark mode? Please answer below:

Did you introduce any new environment variables? If so, call them out explicitly here:

Did you use AI for this? If so, how much did it assist you?


[!NOTE] Replaces legacy markdown and ReactMarkdown with a Lexical-based editor/reader, adds server-side HTML generation, and exposes lexicalState/html on Item/Sub via GraphQL while updating UI to use SNInput.

  • Frontend:
    • Editor/Reader: Add SNEditor/SNReader, toolbar, uploads, mentions, links, preview, code (Shiki), math (KaTeX), media, gallery, TOC, footnotes, and supporting nodes/plugins.
    • Components: Replace MarkdownInput with SNInput; switch Text rendering to Lexical; update comment/item/bio forms, TOC, media handling, and styles.
    • UX: Overflow handling, tooltip tweaks, dark-mode code theming.
  • Backend/API:
    • GraphQL: Add lexicalState and html fields to Item/Sub with resolvers using a lexicalStateLoader (DataLoader) and SSR HTML generator.
    • SSR: Wire lexicalStateLoader into Apollo contexts for API and SSR.
  • Infra/Deps:
    • Add Lexical, mdast utilities, KaTeX, Shiki, DOMPurify, LinkeDOM; remove remark/rehype/react-markdown/highlight-related code; minor Next.js alias for canvas.

Written by Cursor Bugbot for commit bf4dcce03a99be2537e7a8bb97ed8001ab00f3a6. This will update automatically on new commits. Configure here.

Soxasora avatar Dec 17 '25 13:12 Soxasora

[!WARNING] Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm linkedom is 92.0% likely obfuscated

Confidence: 0.92

Location: Package overview

From: package-lock.jsonnpm/[email protected]

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at [email protected].

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/[email protected]. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

socket-security[bot] avatar Dec 17 '25 13:12 socket-security[bot]

Cannot update a component (SNReader) while rendering a different component (LexicalExtensionComposer). To locate the bad setState() call inside LexicalExtensionComposer, follow the stack trace as described in https://reactjs.org/link/setstate-in-render Error Component Stack

alternative pattern using context:

import dynamic from "next/dynamic"
import { createContext, useContext } from "react"
import { useRouter } from "next/router"

const HtmlContext = createContext("")

function HtmlFallback() {
  const html = useContext(HtmlContext)
  return <div dangerouslySetInnerHTML={{ __html: html }} />
}

const Reader = dynamic(() => import("./reader"), {
  ssr: false,
  loading: () => <HtmlFallback />,
})

export function SNReader({ html, ...props }) {
  const router = useRouter()

  // optional: avoid query flicker on first render in some cases
  const showDebugHtml = router.isReady && router.query.html != null

  if (showDebugHtml) {
    return <div dangerouslySetInnerHTML={{ __html: html }} />
  }

  return (
    <HtmlContext.Provider value={html}>
      <Reader {...props} />
    </HtmlContext.Provider>
  )
}

huumn avatar Dec 19 '25 00:12 huumn

This fits perfectly, thank you!

Soxasora avatar Dec 19 '25 00:12 Soxasora

This PR can be merged without the previous one as QA is being done here, and new changes and fixes are being pushed here.

If it's better to do the original PR and then the cleanup PR, I can un-draft

Soxasora avatar Dec 19 '25 16:12 Soxasora