Skip to content

fix(data): preserve historical metadata integrity#194

Open
afc163 wants to merge 2 commits into
mainfrom
codex/fix-historical-metadata-integrity
Open

fix(data): preserve historical metadata integrity#194
afc163 wants to merge 2 commits into
mainfrom
codex/fix-historical-metadata-integrity

Conversation

@afc163

@afc163 afc163 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

  • Keep historical component props sourced from their own snapshot/docs instead of copying from the latest major snapshot.
  • Clone resolved components before runtime backfills so resolving one command cannot mutate cached historical metadata used by later commands.

Verification

  • npm run build
  • npm run typecheck
  • Focused loader tests — 96 passed
  • npm audit --json — 0 vulnerabilities on the base dependency set
  • git diff --check

The default parallel full suite was also run; two existing lint --diff tests intermittently exceeded the 5-second test timeout under load. Both exact tests pass in isolation, and no lint files are changed in this PR.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

本次变更调整属性差异和版本比较规则,限制元数据缓存容量,并收紧组件 major 快照回填范围;测试覆盖预发布版本排序、缓存淘汰、属性移除和缓存快照隔离行为。

Changes

核心数据行为

Layer / File(s) Summary
属性差异结果调整
src/commands/changelog.ts, src/__tests__/commands/changelog.test.ts
导出 diffProps,并使移除的属性仅保留 nametype,不再推断 replacement
版本比较与元数据缓存
src/data/version.ts, src/data/loader.ts, src/__tests__/version-loader.test.ts
版本解析优先使用 semver.parse 并支持预发布版本;元数据缓存限制为 32 条并淘汰最早条目。
组件快照解析与回填
src/data/loader.ts, src/__tests__/loader-internal.test.ts, src/__tests__/version-loader.test.ts
resolveComponent 操作缓存对象的浅拷贝,major 快照仅回填缺失描述,不再复制 propssubComponentProps

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Poem

小兔蹦跳看缓存,
三十二格不乱撞。
预发布版本排好队,
属性移除不乱猜。
快照轻拷不受伤,
描述回填暖洋洋。

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题概括了本次关于历史元数据处理与完整性保护的核心改动,且与实际变更一致。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-historical-metadata-integrity

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.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

📦 Package Size Report

Metric Size Diff
Packed 25.69 MB -0.04 KB (-0.0%)
Unpacked 26.77 MB -0.37 KB (-0.0%)

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.69%. Comparing base (abbbe31) to head (12eaaf5).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #194      +/-   ##
==========================================
- Coverage   99.69%   99.69%   -0.01%     
==========================================
  Files          40       40              
  Lines        2603     2598       -5     
  Branches      803      800       -3     
==========================================
- Hits         2595     2590       -5     
  Misses          8        8              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the metadata loading and changelog generation logic. Key changes include limiting the metadata cache to 32 entries with an eviction policy, removing major-version API data backfilling (restricting backfills to descriptions), updating semver comparison to correctly handle prerelease identifiers, and removing speculative rename guessing for removed properties. The reviewer feedback highlights three main improvement opportunities: cleaning up dead code (replacement property and printing logic) left over from removing speculative renames, replacing the while loop with an if statement in the cache eviction logic for better readability, and performing a deeper clone of storedComp in resolveComponent to prevent nested arrays and objects from mutating the shared cache.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/commands/changelog.ts Outdated
(p) => p.type === prop.type && !oldMap.has(p.name),
);
removed.push({ name, type: prop.type, replacement: possibleRename?.name });
removed.push({ name, type: prop.type });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

With the removal of the speculative rename inference, the replacement property is no longer populated in the removed prop diffs. However, the PropDiff interface still defines replacement?: string; and printApiDiff still contains logic to print the replacement note (lines 286-288). This results in dead code.\n\nConsider removing replacement from the PropDiff interface and cleaning up the unused printing logic in printApiDiff to keep the codebase clean and maintainable.

Comment thread src/data/loader.ts Outdated
Comment on lines +106 to +110
while (metadataCache.size > MAX_METADATA_CACHE_ENTRIES) {
const oldestKey = metadataCache.keys().next().value;
if (oldestKey === undefined) break;
metadataCache.delete(oldestKey);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since metadataCache.set is only called when the key does not already exist in the cache, the cache size can only exceed MAX_METADATA_CACHE_ENTRIES by at most 1. Using an if statement instead of a while loop is more idiomatic, improves readability, and avoids any theoretical risk of an infinite loop.

Suggested change
while (metadataCache.size > MAX_METADATA_CACHE_ENTRIES) {
const oldestKey = metadataCache.keys().next().value;
if (oldestKey === undefined) break;
metadataCache.delete(oldestKey);
}
if (metadataCache.size > MAX_METADATA_CACHE_ENTRIES) {
const oldestKey = metadataCache.keys().next().value;
if (oldestKey !== undefined) {
metadataCache.delete(oldestKey);
}
}

Comment thread src/data/loader.ts
suggestion ? `Did you mean '${suggestion}'?` : undefined,
);
}
const comp = { ...storedComp };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The shallow copy { ...storedComp } only copies the top-level properties of the component. Nested arrays and objects, such as props and subComponentProps, still share the same references as the cached storedComp. If a caller of resolveComponent mutates these arrays or objects (e.g., sorting, filtering, or modifying elements), it will pollute the shared metadata cache.\n\nTo ensure true immutability of the cached metadata, consider cloning the props array and subComponentProps object as well.

  const comp = {
    ...storedComp,
    props: [...storedComp.props],
    subComponentProps: storedComp.subComponentProps
      ? Object.fromEntries(
          Object.entries(storedComp.subComponentProps).map(([key, val]) => [key, [...val]]),
        )
      : undefined,
  };

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