Skip to content

feat(article): parse, render and translate X articles on a reader page - #22

Merged
Chilfish merged 8 commits into
mainfrom
feat/x-article
Sep 27, 2026
Merged

Chilfish merged 8 commits into
mainfrom
feat/x-article

Conversation

@Chilfish

Copy link
Copy Markdown
Owner

改了什么

X 长文(X Article)从「正文里一条裸链接」升级为结构化块文档,在独立阅读页 /article/:id 渲染与按块翻译:

  • 请求:TweetRequests.details() 补 fieldToggles(JSON 字符串,开启 withArticleRichContentState / withArticlePlainText)
  • 解析:article.article_results.result → TweetArticle 块文档(Draft.js content_state;段落/标题/列表/引用/markdown/图片/嵌入帖/分隔线);行内 run 按「实体边界 ∪ 样式边界」切分(修复段首 Bold 被整段误加粗)
  • 渲染:ArticleReader(标题/作者/封面/正文)+ 块组件,react-markdown + remark-gfm 渲染 GFM 表格;推文内为紧凑卡 TweetArticleCard
  • 内嵌推文:loader 并行取回 TWEET 实体并注入真实只读推文组件,取数失败回退链接卡
  • 翻译:按块 key 对齐、<<__LINK_n__>> 占位符锚定,持久化到 localCache + 新表 tweet_article_translations(含 migration);阅读页三态开关(原文/双语/仅译文)
  • 工具栏:返回 / 三态 / 重试 / 一键截图 + 更多菜单(设置/分享/复制/下载图片)
  • 修复:Storybook Vite 配置改用 Vite 8 原生 resolve.tsconfigPaths

为什么

x.com/<user>/article/<id> 型推文的标题与正文此前完全丢失(只返回 t.co 裸链接)。方案见 docs/features/tweet/article.md 与 ADR-009。

检查项

  • bun run typecheck、bun run lint、bun run build 通过
  • bun run verify/index.ts --exit-on-fail 通过(pre-push 全绿:typecheck / eslint / verify / build / build-storybook)
  • 新行为有对应测试和验收标准(AC-TWEET-012~019)
  • 提交信息符合 Conventional Commits
  • 文档已同步(feature doc / ADR / dev log / INDEX / backlog / llms+OpenAPI+skill 快照)

验证方式

  • bun run verify/index.ts --ac AC-TWEET-012 ~ AC-TWEET-019(离线:解析 / 行内样式切分 / 内嵌推文 / 三态渲染)
  • 本地 dev 实测 SSR 200:/article/2103576349499855160(rich,81 块)、/article/2099885132379500562(GFM 表格)、/article/2103463356913098908(段首 Bold)、/article/2104076282107723935(4 条内嵌推文)
  • Storybook:Article/Body、Article/Reader(rich / markdown / plain / 封面 / 附言 / 内嵌推文 / 三态)、Article/Page(可交互三态)、Article/Toolbar、Tweet/ArticleCard

遗留(非阻塞):线上库需执行 db:migrate;可选 plain-article/:id 截图路由;LLM 分批翻译需真机验证。

Chilfish and others added 8 commits September 27, 2026 14:20
- request article rich content via TweetRequests.details() fieldToggles
  (JSON string, not object — X silently ignores the axios-serialized form)
- map the article node to a TweetArticle block document parsed from
  Draft.js content_state (paragraphs, headings, lists, markdown, media,
  embeds); split inline runs at entity AND style boundaries so partial
  Bold/Italic ranges are not smeared across the whole block
- render blocks on a dedicated /article/:id page with reader toolbar,
  three-state translation and real inline-tweet embeds (parallel fetch,
  graceful fallback to a link card)
- translate articles per block key with placeholder anchoring; persist to
  localCache + new tweet_article_translations table (migration)
- add react-markdown + remark-gfm for MARKDOWN (GFM) entities
- backfill flat article caches at the getLocalTweet exit

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- fixture-driven parse tests: content_state blocks, entity + style run
  splitting (partial Bold no longer smears the whole block), and
  collectEmbeddedTweetIds dedupe/order
- acceptance render tests: three-state translation, embed delegation and
  fallback; AC-TWEET-012~019 criteria updated to v1.6
- freeze real upstream fixtures via scripts/tmp-dump-article.ts and allow
  irregular whitespace in fixture data

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- feature doc docs/features/tweet/article.md + ADR-009 + dev log 2026-09-27
- INDEX / backlog / development-log index updates
- llms.txt Data Shapes + OpenAPI schemas (TweetArticle, ArticleBlock,
  ArticleTranslation) and the article request/response fields
- regenerate the bundled skill OpenAPI snapshot and update SKILL.md

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
The .storybook Vite config imported `vite-tsconfig-paths`, which is not
declared in package.json and not installed, so `bun run build-storybook`
(the pre-push gate) failed to resolve it. Vite 8 resolves tsconfig paths
natively, so drop the plugin and opt in via `resolve.tsconfigPaths`.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- pull the full-article card out of the /article route into ArticleReader,
  so every reader form is presentational and storyable
- move serializeArticleBlock into lib/article/serialize and stop the
  lib/article barrel from re-exporting translate: translate imports the
  server-only ai-timeout (reads `process`), which must not reach the
  client / Storybook module graph

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
- article.fixtures reuses test/fixtures/articles through mapArticle, so
  stories and ACs share one data source
- cover ArticleBody (blocks / three-state / embed injection) and the
  ArticleReader full-article forms (rich, markdown, plain fallback, cover
  presence, commentary, embedded tweets real + fallback) plus an
  interactive ArticlePage with the toolbar
- add small stories for ArticleToolbar / OptionsMenu / TranslateToggle,
  the inline runs + translated runs, and TweetArticleCard
- ac-ui.spec now requires a story for every article component

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
anon-tweet Ready Ready Preview Sep 27, 2026 6:40am UTC

@Chilfish
Chilfish merged commit 2af7a9a into main Sep 27, 2026
2 checks passed
@Chilfish
Chilfish deleted the feat/x-article branch September 27, 2026 06:42

This branch was successfully deployed

1 active deployment
Preview — 621f5fc3 Deployed Sep 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant