Skip to content

阅读 MD 增强#98

Merged
AmintaCCCP merged 35 commits intoAmintaCCCP:mainfrom
SummerRay160:ReadMD-UP
Apr 25, 2026
Merged

阅读 MD 增强#98
AmintaCCCP merged 35 commits intoAmintaCCCP:mainfrom
SummerRay160:ReadMD-UP

Conversation

@SummerRay160
Copy link
Copy Markdown
Contributor

@SummerRay160 SummerRay160 commented Apr 24, 2026

Summary by CodeRabbit

  • New Features

    • Added OpenAI‑compatible API option
    • Per‑channel "load more" discovery state and controls
    • New plain‑text Markdown stripping utility
  • Bug Fixes & Improvements

    • README modal: improved TOC, smooth navigation, progress bar, Back‑to‑Top and suppressed scroll controls while open
    • More stable, accessible Markdown rendering, image zoom/pan, code styling, and scrollbar/typography tweaks
  • Tests

    • Vitest config, setup, and comprehensive renderer/util test suites
  • Chores

    • Added test scripts and testing/dev tooling; enabled Tailwind typography plugin

HappySummer and others added 14 commits April 23, 2026 20:26
refactor(RepositoryEditModal): 修复自定义描述为空时的处理逻辑
移除传统的分页组件,改为实现无限滚动加载模式
新增加载更多按钮组件,优化大数据量下的用户体验
简化页码管理逻辑,减少不必要的状态和计算
添加 discoveryIsLoadingMore 和 discoveryLoadMoreError 状态用于管理加载更多操作
实现加载更多时的滚动定位和错误处理
优化频道切换时的状态重置逻辑
扩展 AI 服务类型以支持兼容 OpenAI API 的自定义端点,包括:
1. 在类型定义中添加 'openai-compatible' 选项
2. 修改请求处理逻辑以直接使用自定义端点的完整 URL
3. 在配置面板中增加相关说明和最终请求地址预览
当选择搜索频道时,显示不同的空状态UI和提示信息,包括搜索图标和搜索引导文本,而非默认的刷新按钮和数据提示
实现使用IndexedDB存储和加载仓库AI分析结果,包括保存、加载、批量加载和删除功能。在DiscoveryView组件中集成该存储服务,确保分析结果在页面刷新后仍可保留。
调整卡片悬停时的阴影、边框和位移效果,统一桌面安全模式和非安全模式下的交互体验
改进标题ID生成算法以避免重复,添加对Markdown格式文本的处理
优化目录提取逻辑,正确处理重复标题并去除Markdown格式
添加错误处理以防止平滑滚动失败时页面无法滚动
添加 Tailwind Typography 插件以增强 Markdown 渲染样式
重构 ReadmeModal 组件,移除进度条并优化 TOC 显示
为模态框添加状态管理,自动隐藏底部导航按钮
重构 MarkdownRenderer 组件,优化代码块样式和暗色模式支持
添加自定义滚动条样式,提升阅读体验
将提示信息从“当前页面所有项目”改为“已加载的所有项目”,以更准确地反映实际分析范围
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Vitest testing and setup, Tailwind typography, README modal TOC/scrolling and stable heading IDs, readmeModalOpen store flag with UI suppression for scroll buttons, per-channel discovery load-more state and persistence tweaks, DB open timeout/onLate handling, and extensive Markdown renderer/CSS refactors.

Changes

Cohort / File(s) Summary
Tooling & tests
package.json, vitest.config.ts, src/test/setup.ts, src/components/MarkdownRenderer.test.tsx, src/utils/markdownUtils.test.ts
Adds Vitest config, test scripts, test setup, and new tests for MarkdownRenderer and markdown utilities.
Styling & Tailwind
tailwind.config.js, src/index.css
Enables @tailwindcss/typography, reorganizes prose/code/pre styles into @layer components, adds .readme-scrollbar, and updates dark-mode hljs/token selectors and scrollbar theming.
Markdown rendering
src/components/MarkdownRenderer.tsx
Refactors to typed markdownComponents, stable/disambiguated heading IDs, hash-link smooth-scroll handling, centralized image-zoom close logic, normalized codeText rendering, and plugin wiring updates.
Readme modal & TOC
src/components/ReadmeModal.tsx
Parses TOC to h1–h6, normalizes labels, generates disambiguated heading keys, container-scoped smooth scrolling with fallbacks, scroll progress/back-to-top state, and IntersectionObserver-driven active-heading tracking; syncs modal open state to store.
Scroll controls
src/components/BackToTop.tsx, src/components/ScrollToBottom.tsx
Both read readmeModalOpen from store and suppress visibility/interactivity (class, aria-hidden, tabIndex) when modal is open.
Discovery UI & store types
src/components/DiscoveryView.tsx, src/store/useAppStore.ts, src/types/index.ts
Discovery selection handlers short-circuit redundant selections; store/types add readmeModalOpen, per-channel discoveryIsLoadingMore and discoveryLoadMoreError, setReadmeModalOpen, and persisted discoveryChannels with adjusted rehydration and channel-reset behavior.
Analysis storage
src/services/discoveryAnalysisStorage.ts
withTimeout adds onLate callback and isSettled; DB open calls use 3000ms timeout and close late DB connections via onLate.
Markdown utilities
src/utils/markdownUtils.ts
Adds exported stripMarkdownFormatting to remove markdown wrappers while preserving displayed text.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant UI as Readme Modal / DiscoveryView
    participant Store as App Store
    participant API as Server/API
    participant DB as Analysis Storage

    U->>UI: Click "Load more" / Open README
    UI->>Store: setDiscoveryLoadingMore(channel, true)
    UI->>API: fetch next page for channel
    API-->>UI: return repos
    UI->>DB: loadAllAnalyses() withTimeout(3000, onLate -> close)
    alt DB responds before timeout
        DB-->>UI: analyses
    else DB late
        DB->>DB: onLate callback -> close late connection
    end
    UI->>Store: append repos, setDiscoveryLoadingMore(channel,false)
    Store->>UI: render updated repo list / modal open state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • PR #90: Overlaps MarkdownRenderer, ReadmeModal, scroll-control, and store changes — large, directly related edits.
  • PR #81: Touches the same modal/renderer/scroll/button code and store readmeModalOpen integration.
  • PR #95: Related changes to discovery pagination and per-channel load-more state in the store and DiscoveryView.

Poem

🐇 I nibble headings, count each twin,
I hush the buttons when the modal’s in.
Scrollbars gleam and code blocks glow,
Tests hop in to help things flow.
Tiny paws tidy prose — off we go! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title '阅读 MD 增强' (README MD Enhancement) is thematic but vague—it doesn't clearly specify which main change should be prioritized among the numerous updates across testing setup, modal behavior, heading IDs, and styling improvements. Consider a more specific title that captures the primary objective, e.g., '增强 README 模态框体验与 Markdown 渲染' (Enhance README modal experience and Markdown rendering) or '添加 Vitest 测试框架与改进 README 阅读体验' (Add Vitest testing framework and improve README reading experience).
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/components/RepositoryEditModal.tsx (1)

723-760: Inconsistent defensive handling of formData.description.

The hardening to (formData.description || '').trim() was applied to lines 647, 652, 723, and 280/460-462, but two sibling branches still access formData.description directly:

  • Line 640: formData.description.length > 0
  • Line 748: formData.description.trim() !== ''

If the intent is to defend against null/undefined sneaking into formData.description, these two spots will still throw (and line 748 sits in the same ternary chain as the guarded line 723, making the inconsistency more visible). If formData.description is guaranteed to be a string (it's initialized to '' and every setter passes a string), the added || '' guards elsewhere are unnecessary.

Pick one direction and apply it consistently.

♻️ Proposed fix — consistent guarding
-              {formData.description.length > 0 ? `${formData.description.length} ${t('字符', 'chars')}` : t('无内容', 'Empty')}
+              {(formData.description || '').length > 0 ? `${(formData.description || '').length} ${t('字符', 'chars')}` : t('无内容', 'Empty')}
-          ) : editIntent.description === 'keep-custom' && formData.description.trim() !== '' && !customStatus.description ? (
+          ) : editIntent.description === 'keep-custom' && (formData.description || '').trim() !== '' && !customStatus.description ? (

Alternatively, if formData.description is trusted to always be a string, revert the other (formData.description || '') additions and rely on the type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RepositoryEditModal.tsx` around lines 723 - 760, Inconsistent
defensive access to formData.description can cause runtime errors; make handling
uniform by always guarding against null/undefined (e.g. replace direct uses of
formData.description.length > 0 and formData.description.trim() !== '' with
(formData.description || '').length > 0 and (formData.description || '').trim()
!== ''), keeping the current guarded checks (e.g. existing (formData.description
|| '').trim()) and leave checks on editIntent.description and
customStatus.description unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/MarkdownRenderer.tsx`:
- Around line 783-797: The inline code renderer destructures an unused node
variable which ESLint flags and risks being forwarded via {...props}; update the
renderer signature from "({ className, children, node, ...props }) =>" to "({
className, children, ...props }) =>" and keep the rest of the logic the same
(preserve isInline, match / language extraction, and the CodeBlock usage) so
node is no longer present or passed through.
- Around line 706-716: getHeadingId can produce duplicate IDs because headingIds
contains generated IDs like "heading-0" while the fallback returns the same
pattern using headingCounter; update getHeadingId to avoid collisions by
namespacing or offsetting the fallback IDs: either (a) prefix fallback IDs
(e.g., "fallback-heading-") when returning `heading-${headingCounter++}` so they
never collide with ReadmeModal.extractToc IDs, or (b) on initialization when
headingIds is present seed/advance headingCounter to a value greater than any
numeric suffix in headingIds (compute max numeric suffix from headingIds values
and set headingCounter = max+1) so subsequent fallback IDs won’t collide; apply
this change around getHeadingId and where headingCounter/headingIds are
initialized (refer to getHeadingId, headingIds, headingTextCountMap,
headingCounter, and ReadmeModal.extractToc).

In `@src/index.css`:
- Line 232: The font-family declaration contains a quoted single-word font
'Consolas' which violates the font-family-name-quotes rule; update the
font-family list in the CSS (the font-family declaration line) by removing the
quotes around Consolas only, leaving multi-word names like 'Cascadia Code',
'Fira Code', and 'JetBrains Mono' quoted so stylelint no longer flags the rule.

In `@src/services/discoveryAnalysisStorage.ts`:
- Around line 16-39: withTimeout can leak an open IDBDatabase when the timeout
rejects before openDb() resolves; fix by changing withTimeout<T>(promise,
timeoutMs, onLateResolve?) to, when the timeout fires, attach promise.then(value
=> onLateResolve?.(value)). This ensures that if openDb() (the IDBOpenDBRequest
resolving to an IDBDatabase) completes after the timeout you invoke the provided
cleanup (e.g., pass db => db.close() at each withTimeout(openDb(), ... ) call
site). Keep openDb as-is (it returns IDBDatabase) and only add the optional
cleanup callback to withTimeout so late resolutions can be closed to avoid
leaking connections and blocking future onupgradeneeded.

In `@src/store/useAppStore.ts`:
- Around line 1182-1208: The setSelectedDiscoveryChannel updater currently
always clears discoveryRepos[selectedDiscoveryChannel], causing unnecessary
wipes and refetches when the user re-selects the same tab; update the
setSelectedDiscoveryChannel function in useAppStore.ts to early-return/no-op
when the new selectedDiscoveryChannel equals the current
state.selectedDiscoveryChannel (i.e., add a same-channel guard) and add a brief
inline comment above the reset logic explaining that repos are reset when
switching channels to trigger an auto-fetch for empty channels (but should not
reset when re-selecting the active channel).

---

Nitpick comments:
In `@src/components/RepositoryEditModal.tsx`:
- Around line 723-760: Inconsistent defensive access to formData.description can
cause runtime errors; make handling uniform by always guarding against
null/undefined (e.g. replace direct uses of formData.description.length > 0 and
formData.description.trim() !== '' with (formData.description || '').length > 0
and (formData.description || '').trim() !== ''), keeping the current guarded
checks (e.g. existing (formData.description || '').trim()) and leave checks on
editIntent.description and customStatus.description unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0fe2b535-d668-45fa-9fc8-cb8f33b21ed2

📥 Commits

Reviewing files that changed from the base of the PR and between a1d6276 and 6c0ffc0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • package.json
  • server/src/routes/proxy.ts
  • src/components/BackToTop.tsx
  • src/components/DiscoveryView.tsx
  • src/components/MarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/components/RepositoryEditModal.tsx
  • src/components/ScrollToBottom.tsx
  • src/components/SubscriptionRepoCard.tsx
  • src/components/settings/AIConfigPanel.tsx
  • src/index.css
  • src/services/aiService.ts
  • src/services/discoveryAnalysisStorage.ts
  • src/store/useAppStore.ts
  • src/types/index.ts
  • src/utils/apiUrlBuilder.ts
  • tailwind.config.js

Comment thread src/components/MarkdownRenderer.tsx Outdated
Comment thread src/components/MarkdownRenderer.tsx Outdated
Comment thread src/index.css Outdated
Comment thread src/services/discoveryAnalysisStorage.ts
Comment thread src/store/useAppStore.ts
SummerRay160 and others added 2 commits April 24, 2026 16:28
当用户重复选择当前已选频道时,避免不必要的滚动位置保存和更新操作
@AmintaCCCP
Copy link
Copy Markdown
Owner

PR #98 审计:阅读 MD 增强

🟢 正面评价

  1. 模态框遮盖修复BackToTopScrollToBottom 在 README 模态框打开时隐藏,并正确同步 aria-hiddentabIndex,无障碍处理到位
  2. TOC 重复标题处理headingTextCountMap + stripMarkdownFormatting 解决了重复标题 ID 冲突,extractTextFromChildren 对嵌套 React 节点的文本提取也写得不错
  3. markdownComponents 提取 — 把内联 components 对象提到变量,可读性改善
  4. @tailwindcss/typography 引入 — 正确的依赖添加路径(devDeps + tailwind.config.js plugin)
  5. ReadmeModal — 移除未使用的 scrollProgress 状态和滚动条 UI,用 readmeScrollbar CSS class 统一了滚动条样式,方向正确
  6. scrollToHeading 加了 try/catch — 防御性编码,好

🔴 需修复

1. Heading ID 冲突风险MarkdownRenderer.tsx

headingIds map 里的 ID 是 heading-0, heading-1...,而 fallback 也生成 heading-${headingCounter++},从 0 开始。如果任何 h4/h5/h6 或包含图片语法的标题走到 fallback,就会跟已有 ID 碰撞,产生 DOM 重复 ID。

修复方案: fallback ID 用不同前缀(如 heading-extra-),或者 headingCounter 初始化为 headingIds.size


🟡 建议修复

2. 未使用的 node 解构MarkdownRenderer.tsx ~L783)

code: ({ className, children, node, ...props }) => {

node 未使用,ESLint 会报错,且 ...props 会把 node 转发给 DOM。直接删掉 node

3. CSS font-family 中 Consolas 引号问题index.css L232)

'Consolas' 是单字标识符,不应加引号,stylelint 会报 font-family-name-quotes。改为 Consolas(无引号),多字名保持引号。

4. withTimeout IDB 连接泄漏discoveryAnalysisStorage.ts L16-39)

timeout 先触发时,openDb() 返回的 IDBDatabase 不会被 .close(),可能积累空闲连接甚至阻塞后续 onupgradeneeded。建议给 withTimeoutonLate 回调,调用处传 db => db.close()

5. setSelectedDiscoveryChannel 缺少同频道守卫useAppStore.ts ~L1182-1208)

点当前已激活的 tab 会无脑清空 repos 触发重新 fetch。加个 if (state.selectedDiscoveryChannel === selectedDiscoveryChannel) return 守卫,并补一行注释说明 channel 切换时 reset 的意图。


⚪ 观察项(不影响合并)

  • prose 组件样式重排 — index.css 里把散落在末尾的 prose 样式移进了 @layer components {},逻辑重组,合理。但 @tailwindcss/typography 已引入,后续可考虑用 @apply 或直接用 typography plugin 的 prose class 替代部分手写样式,减少维护负担
  • 暗色模式 hljs 样式补全 — 加了 .dark .hljs-params, .dark .hljs-property 等,补了之前的遗漏,没坏处
  • DiscoveryView 文案调整 — "当前页面" → "已加载的",语义更准确,OK
  • 移除了 github-dark.min.css 导入 — 改为通过 .dark scope 手写覆盖,可以但要注意后续 hljs 版本升级时手动同步

结论

1 个必须修复(heading ID 冲突),4 个建议修复。冲突问题在复杂 README(含 h4+ 或图片标题)下必现,应在合并前处理。其余几项是 Lint 和防御性编码问题,建议一并修掉。

HappySummer added 3 commits April 24, 2026 17:09
调整代码块字体设置,移除Consolas的引号以修复显示问题
修改标题ID生成逻辑,使用headingIds作为初始值并添加extra前缀
添加onLate回调参数以处理超时后的资源清理
重构withTimeout函数以支持更健壮的超时处理
在所有数据库操作中添加超时后的数据库连接关闭
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/components/DiscoveryView.tsx (1)

861-871: Good no-op guard on channel re-selection.

Short-circuiting when the user taps the already-active channel prevents the setSelectedDiscoveryChannel reducer in src/store/useAppStore.ts (lines 1182–1208) from wiping discoveryRepos, discoveryNextPage, discoveryHasMore, discoveryTotalCount, discoveryIsLoadingMore, and discoveryLoadMoreError for the current channel, which would otherwise force an unnecessary re-fetch via the auto-fetch effect at lines 601–615.

Optional nit: the guard + scroll-position save + setSelectedDiscoveryChannel block is now duplicated between the mobile (862–871) and desktop (881–890) handlers. Consider extracting into a small handleChannelSelect callback inside the component to keep the two call sites in sync going forward.

♻️ Optional extraction
+  const handleChannelSelect = useCallback((channel: DiscoveryChannelId) => {
+    if (channel === selectedDiscoveryChannel) return;
+    if (scrollContainerRef.current) {
+      const scrollTop = scrollContainerRef.current.scrollTop;
+      discoveryScrollPositionsRef.current[selectedDiscoveryChannel] = scrollTop;
+      setDiscoveryScrollPosition(selectedDiscoveryChannel, scrollTop);
+    }
+    setSelectedDiscoveryChannel(channel);
+  }, [selectedDiscoveryChannel, setDiscoveryScrollPosition, setSelectedDiscoveryChannel]);

Then reuse it in both MobileTabNav and DiscoverySidebar onChannelSelect props.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DiscoveryView.tsx` around lines 861 - 871, The onChannelSelect
logic is duplicated in MobileTabNav and DiscoverySidebar; extract a shared
handler (e.g., handleChannelSelect) inside the DiscoveryView component that
accepts a channel and performs the same steps: no-op if channel ===
selectedDiscoveryChannel, save scrollTop into
discoveryScrollPositionsRef.current[selectedDiscoveryChannel] and call
setDiscoveryScrollPosition(selectedDiscoveryChannel, scrollTop) when
scrollContainerRef.current exists, then call
setSelectedDiscoveryChannel(channel); replace both inline onChannelSelect props
with this new handleChannelSelect to keep behavior consistent across
MobileTabNav and DiscoverySidebar.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/MarkdownRenderer.tsx`:
- Around line 696-716: The heading text extraction currently returns raw
concatenated child text in extractTextFromChildren, causing mismatches with
ReadmeModal.extractToc keys; update extractTextFromChildren to normalize the
extracted text the same way stripMarkdownFormatting does (or import and call
stripMarkdownFormatting) so that getHeadingId uses normalized keys for
headingTextCountMap and headingIds lookups, ensuring images, extra/multiple
whitespace, and leading/trailing spaces are collapsed/trimmed consistently and
preventing fallback to heading-extra-* IDs.

In `@src/services/discoveryAnalysisStorage.ts`:
- Around line 32-56: The withTimeout helper currently calls onLate when the
primary promise resolves before the timeout (inverting the intended logic) and
keeps an unused lateValue; fix by inverting the guard so onLate is invoked only
when the timeout already fired (i.e. call onLate when isSettled === true),
remove the unused lateValue binding, and ensure the wrapped promise has a
.catch(() => {}) to swallow any late rejection after the timeout; use the
existing symbols withTimeout, isSettled, onLate, wrappedPromise, and
timeoutPromise to locate and apply these changes.

---

Nitpick comments:
In `@src/components/DiscoveryView.tsx`:
- Around line 861-871: The onChannelSelect logic is duplicated in MobileTabNav
and DiscoverySidebar; extract a shared handler (e.g., handleChannelSelect)
inside the DiscoveryView component that accepts a channel and performs the same
steps: no-op if channel === selectedDiscoveryChannel, save scrollTop into
discoveryScrollPositionsRef.current[selectedDiscoveryChannel] and call
setDiscoveryScrollPosition(selectedDiscoveryChannel, scrollTop) when
scrollContainerRef.current exists, then call
setSelectedDiscoveryChannel(channel); replace both inline onChannelSelect props
with this new handleChannelSelect to keep behavior consistent across
MobileTabNav and DiscoverySidebar.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 652a4539-19c9-4254-a700-e3cb64c54caf

📥 Commits

Reviewing files that changed from the base of the PR and between 404c131 and f50d8eb.

📒 Files selected for processing (4)
  • src/components/DiscoveryView.tsx
  • src/components/MarkdownRenderer.tsx
  • src/index.css
  • src/services/discoveryAnalysisStorage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/index.css

Comment thread src/components/MarkdownRenderer.tsx Outdated
Comment thread src/services/discoveryAnalysisStorage.ts
@SummerRay160
Copy link
Copy Markdown
Contributor Author

可以测试后合并

@AmintaCCCP
Copy link
Copy Markdown
Owner

可以测试后合并
最新编译的桌面端发现两个小问题:

  1. 趋势页AI分析后还是不能持久化保存
  2. Readme大纲有一些仓库解析有问题,有的锚点解析出来点不了,还有的锚点定位有问题

HappySummer added 2 commits April 24, 2026 18:51
调整超时处理逻辑,修正条件判断错误并添加对异步操作错误的处理
refactor(ReadmeModal): 重构目录导航和滚动功能
feat(MarkdownRenderer): 增强链接和图片处理逻辑
test: 添加markdownUtils和MarkdownRenderer的单元测试
build: 配置Vitest测试环境
style: 调整代码块和目录样式
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/components/MarkdownRenderer.tsx (1)

773-811: ⚠️ Potential issue | 🟠 Major

extractTextFromChildren still doesn't match ReadmeModal's key normalization — headings with images or irregular whitespace miss the map and fall back to heading-extra-*.

ReadmeModal.extractToc builds idMap keys via stripMarkdownFormatting, which (a) replaces ![alt](url) with alt, (b) collapses \s+ to a single space, and (c) trims. extractTextFromChildren here does none of those, and additionally returns '' for <img> elements because img.props.children is undefined. Concrete mismatches:

  • # ![logo](x) Title → TOC key logo Title, renderer key ' Title' (alt lost).
  • ## Hello world (double space) → TOC key Hello world, renderer key Hello world.
  • ## Title (leading/trailing whitespace in source) → TOC key Title, renderer key depends on parser trimming.

When keys diverge the renderer falls back to heading-extra-${n}, and the corresponding TOC item in ReadmeModal.scrollToHeading won't find #heading-N in the DOM. The fallbackText scan only helps when heading.textContent exactly matches the TOC label, which is also not guaranteed here (e.g., images render as <img> with no text content).

Suggested fix — either reuse stripMarkdownFormatting on the extracted text, or handle the <img> case explicitly and normalize whitespace:

🔧 Proposed fix
+import { stripMarkdownFormatting } from '../utils/markdownUtils';
+
 const extractTextFromChildren = (children: React.ReactNode): string => {
   if (typeof children === 'string') return children;
   if (typeof children === 'number') return String(children);
   if (Array.isArray(children)) return children.map(extractTextFromChildren).join('');
   if (React.isValidElement(children)) {
+    const props = children.props as { children?: React.ReactNode; alt?: string };
+    if (children.type === 'img' || children.type === MarkdownImage) {
+      return props.alt ?? '';
+    }
-    return extractTextFromChildren((children.props as { children?: React.ReactNode }).children);
+    return extractTextFromChildren(props.children);
   }
   return '';
 };
@@
   const getHeadingId = useCallback((children: React.ReactNode): string | undefined => {
     if (headingIds && headingIds.size > 0) {
-      const text = extractTextFromChildren(children);
+      const text = stripMarkdownFormatting(extractTextFromChildren(children));
       const count = headingTextCountMapRef.current.get(text) || 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/MarkdownRenderer.tsx` around lines 773 - 811, The heading id
lookup fails because extractTextFromChildren doesn't normalize text the same way
ReadmeModal.extractToc does; update extractTextFromChildren (and/or
getHeadingId) to return the same canonical key: when encountering React elements
handle <img> by using props.alt (or props['alt'] fallback) instead of children,
collapse consecutive whitespace to a single space, and trim leading/trailing
spaces (or simply call the existing stripMarkdownFormatting utility on the
extracted string if available) before using headingTextCountMapRef/getHeadingId
so keys match ReadmeModal.extractToc's idMap.
🧹 Nitpick comments (2)
src/services/discoveryAnalysisStorage.ts (1)

32-61: Critical fix from prior review is correctly applied — with one small redundancy left.

Tracing the four paths confirms the inverted-guard bug from the previous review (fingerprint 0d479ab2) is resolved:

  • Fast resolve (happy path): isSettled is false at .then, so onLate is skipped and the caller receives an open IDBDatabase. ✓
  • Timeout wins, promise resolves late: isSettled === true when .then runs, so onLate(value) fires and lateDb.close() releases the connection. ✓
  • Early rejection: re-thrown, Promise.race rejects, caller's try/catch logs a warning. ✓
  • Late rejection: Promise.race already observes both promises internally, so the rethrow on Line 50 won't surface as an unhandled rejection. ✓

All four call sites (Lines 67, 89, 124, 161) pass (lateDb) => lateDb.close(), so the original IDB-leak concern is addressed end-to-end.

One tiny follow-up you could fold in later (not blocking):

♻️ Optional polish

The onLate && guard on Line 47 only gates a console.warn, which has nothing to do with cleanup — a late rejection is worth logging even when no onLate is wired up. Renaming isSettledtimedOut also reads more accurately since the flag flips only on the timeout path, not on normal settlement.

-  let isSettled = false;
+  let timedOut = false;

   const wrappedPromise = promise
     .then((value) => {
-      if (isSettled && onLate) {
+      if (timedOut && onLate) {
         onLate(value);
       }
       return value;
     })
     .catch((error) => {
-      if (isSettled && onLate) {
+      if (timedOut) {
         console.warn('[discoveryAnalysisStorage] Late error after timeout:', error);
       }
       throw error;
     });

   const timeoutPromise = new Promise<T>((_, reject) =>
     setTimeout(() => {
-      isSettled = true;
+      timedOut = true;
       reject(new Error('DiscoveryAnalysisStorage timeout'));
     }, timeoutMs)
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/discoveryAnalysisStorage.ts` around lines 32 - 61, The
withTimeout helper currently uses isSettled and gates the console.warn in the
.catch on the presence of onLate; change the flag name isSettled to timedOut to
reflect it only flips on timeout, and remove the onLate guard around the
late-error log so the .catch always logs late rejections (leave the onLate call
in .then as-is to handle late success cleanup). Update references to isSettled
to timedOut and keep the rest of withTimeout (promise wrapping, timeoutPromise,
Promise.race) unchanged.
src/index.css (1)

148-335: LGTM on the prose/scrollbar reorg and hljs dark-mode coverage.

Moving prose styles under @layer components (so they can be overridden by utilities) and scoping the new readme-scrollbar separately from category-scrollbar/scrollbar-auto is a clean split. The extended .dark .hljs-* rules (params/property/punctuation/tag/title) properly backfill what was lost by removing the github-dark.min.css import.

One minor follow-up to keep in mind (not blocking): the structural selectors .prose p > span > span > img and .prose pre div[class*="border-r"] > div are tightly coupled to the exact JSX tree emitted by MarkdownRenderer/CodeBlock. If that structure changes (e.g., another wrapper), these rules will silently stop applying. A class hook on the relevant elements would be more refactor-proof.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.css` around lines 148 - 335, The structural CSS selectors (.prose p
> span > span > img and .prose pre div[class*="border-r"] > div) are fragile;
update the renderer and styles to use class hooks instead: add stable classNames
in the MarkdownRenderer/CodeBlock JSX (e.g., md-image for images and
code-gutter-item for the gutter/div wrapper) and replace the selectors in
src/index.css with .prose .md-image:hover and .prose pre .code-gutter-item:hover
(and their .dark variants), ensuring behavior stays the same but no longer
depends on the exact DOM nesting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ReadmeModal.tsx`:
- Around line 311-321: The TOC indent function tocIndentClass uses a
non-existent Tailwind class 'pl-15' for case 6, so level-6 headings get no
indent; update the case 6 return in tocIndentClass to a valid spacing class
(e.g., 'pl-16') or an arbitrary value (e.g., 'pl-[15px]') so level 6 renders
with the intended indentation.

In `@src/test/setup.ts`:
- Around line 32-33: The variable originalFetch is captured but never used;
either remove the assignment or restore the real fetch in teardown. Fix by
either deleting the line that assigns originalFetch or, if you intend to restore
native fetch between tests, add an afterEach (or afterAll) hook that sets
window.fetch = originalFetch and keep the existing window.fetch = vi.fn();
reference originalFetch, window.fetch, afterEach/afterAll, and vi.fn() to locate
and update the test setup.

---

Duplicate comments:
In `@src/components/MarkdownRenderer.tsx`:
- Around line 773-811: The heading id lookup fails because
extractTextFromChildren doesn't normalize text the same way
ReadmeModal.extractToc does; update extractTextFromChildren (and/or
getHeadingId) to return the same canonical key: when encountering React elements
handle <img> by using props.alt (or props['alt'] fallback) instead of children,
collapse consecutive whitespace to a single space, and trim leading/trailing
spaces (or simply call the existing stripMarkdownFormatting utility on the
extracted string if available) before using headingTextCountMapRef/getHeadingId
so keys match ReadmeModal.extractToc's idMap.

---

Nitpick comments:
In `@src/index.css`:
- Around line 148-335: The structural CSS selectors (.prose p > span > span >
img and .prose pre div[class*="border-r"] > div) are fragile; update the
renderer and styles to use class hooks instead: add stable classNames in the
MarkdownRenderer/CodeBlock JSX (e.g., md-image for images and code-gutter-item
for the gutter/div wrapper) and replace the selectors in src/index.css with
.prose .md-image:hover and .prose pre .code-gutter-item:hover (and their .dark
variants), ensuring behavior stays the same but no longer depends on the exact
DOM nesting.

In `@src/services/discoveryAnalysisStorage.ts`:
- Around line 32-61: The withTimeout helper currently uses isSettled and gates
the console.warn in the .catch on the presence of onLate; change the flag name
isSettled to timedOut to reflect it only flips on timeout, and remove the onLate
guard around the late-error log so the .catch always logs late rejections (leave
the onLate call in .then as-is to handle late success cleanup). Update
references to isSettled to timedOut and keep the rest of withTimeout (promise
wrapping, timeoutPromise, Promise.race) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cc35bc7-87d3-4d54-8eb6-55a14de8357e

📥 Commits

Reviewing files that changed from the base of the PR and between f50d8eb and 8fbebe6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • package.json
  • src/components/MarkdownRenderer.test.tsx
  • src/components/MarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/index.css
  • src/services/discoveryAnalysisStorage.ts
  • src/test/setup.ts
  • src/utils/markdownUtils.test.ts
  • src/utils/markdownUtils.ts
  • vitest.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Comment thread src/components/ReadmeModal.tsx
Comment thread src/test/setup.ts Outdated
移除测试文件中未使用的originalFetch变量
修正ReadmeModal中6级标题的缩进样式从pl-15改为pl-16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ReadmeModal.tsx`:
- Around line 130-173: The IntersectionObserver created inside the setTimeout
callback is never reachable by the effect cleanup, causing a leak; hoist a
variable (e.g., let observer: IntersectionObserver | null = null) in the
useEffect scope (alongside the existing timer variable), assign the new
IntersectionObserver instance to that variable inside the timeout, and before
creating a new observer disconnect any existing one; update the effect's
returned cleanup to clearTimeout(timer) and also disconnect observer if non-null
so observer.disconnect() runs on re-run/unmount; keep references to contentRef,
tocItems, readmeContent and continue using setActiveHeadingId as is.
- Around line 54-75: extractToc is picking up lines starting with # inside
fenced code blocks, producing TOC entries that MarkdownRenderer (which ignores
code fences) will never render; to fix, strip fenced code blocks (both ```...```
and ~~~...~~~) out of content before running the heading-regex (keep using
TOC_MAX_LEVEL and the same regex) so regex.exec runs against the cleaned string,
and then ensure id generation (heading-<n>) still matches
MarkdownRenderer.getHeadingId() for sample READMEs; also re-run the
scrollToHeading checks to confirm no fallback matching is needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d79f706b-f4f9-4ae1-b3c3-1b1d80b56ae0

📥 Commits

Reviewing files that changed from the base of the PR and between 8fbebe6 and 1c55e7e.

📒 Files selected for processing (2)
  • src/components/ReadmeModal.tsx
  • src/test/setup.ts
✅ Files skipped from review due to trivial changes (1)
  • src/test/setup.ts

Comment thread src/components/ReadmeModal.tsx
Comment thread src/components/ReadmeModal.tsx
HappySummer added 3 commits April 24, 2026 21:51
在提取目录时排除代码块内容以避免干扰,同时改进IntersectionObserver的清理逻辑
feat(UI): 优化移动端布局和响应式设计
- 在DiscoveryView和SubscriptionRepoCard中添加移动端适配样式
- 隐藏小屏幕上的非必要按钮以节省空间
- 修复滚动和溢出问题

refactor(aiService): 调整推理参数条件判断逻辑
调整工具提示和平台筛选器的位置及宽度以适应移动端
重构发现视图的侧边栏布局,改为固定定位
移除重复的滚动容器,优化内容区域结构
@SummerRay160
Copy link
Copy Markdown
Contributor Author

可以合并

SummerRay160 and others added 5 commits April 25, 2026 01:12
实现仓库批量还原功能,支持选择还原字段(描述、标签、分类)和目标来源(原始或AI)。新增还原按钮、模态框和相关处理逻辑,提供详细的还原选项和状态统计。当还原分类时自动解锁分类锁定,并支持显示还原结果统计。
当还原到默认来源可能导致AI数据丢失时,显示警告信息提醒用户
添加API密钥解密失败和配置不完整的验证提示
移除不必要的分类锁定条件判断
优化AI分析工作器的中止控制和并发管理
@AmintaCCCP
Copy link
Copy Markdown
Owner

可以合并

有冲突了

HappySummer and others added 3 commits April 25, 2026 03:19
添加 NumberInput、StepperInput 和 SliderInput 组件
在设置面板和搜索栏中使用新组件替换原生输入
优化输入交互体验并添加预设值和验证提示
@SummerRay160
Copy link
Copy Markdown
Contributor Author

SummerRay160 commented Apr 24, 2026

看来黑白化得不够彻底。。。
我的意见是提供黑白/彩色两套UI可供用户切换

@AmintaCCCP
Copy link
Copy Markdown
Owner

现在是深色浅色模式,要再加就得弄换皮肤了

@AmintaCCCP
Copy link
Copy Markdown
Owner

image 趋势左边分类对齐有问题

@AmintaCCCP AmintaCCCP merged commit 0dcc2cf into AmintaCCCP:main Apr 25, 2026
5 checks passed
@SummerRay160
Copy link
Copy Markdown
Contributor Author

这我也不知道怎么改好,之前是因为定位不起效果才做的固定位置。这个你来改吧

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.

2 participants