Skip to content

fix(ui): govern leftover icon sizes with ICON_SIZE - #3127

Open
1625567290 wants to merge 6 commits into
apache:mainfrom
1625567290:fix/ui-icon-size-scale
Open

fix(ui): govern leftover icon sizes with ICON_SIZE#3127
1625567290 wants to merge 6 commits into
apache:mainfrom
1625567290:fix/ui-icon-size-scale

Conversation

@1625567290

Copy link
Copy Markdown
Contributor

Summary

ICON_SIZE and --icon-* already define the chrome / dense / hero rungs. Two product call sites and the icon story still passed raw pixels.

This change:

  • routes the leftover size={16}, size={30}, and story size={20} through ICON_SIZE
  • adds npm run check:icon-size, which parses TSX so comments, strings, and unrelated size props are not false positives
  • runs that check in the CI typecheck job

Fixes #2359

Test plan

  • npm run check:icon-size
  • scripts/check-icon-size.test.mjs

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c8ac3008-fd70-4abb-b6b5-9c4a76446716

📥 Commits

Reviewing files that changed from the base of the PR and between 971aa7a and 9be1c57.

📒 Files selected for processing (1)
  • scripts/check-icon-size.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/check-icon-size.mjs

Included review availability: Your plan includes up to 3 reviews per rolling hour; 0 remain after this review.


📝 Walkthrough

Summary

This PR completes the governed icon-size migration for the remaining size={16}, size={20}, and size={30} call sites. It replaces these values with existing ICON_SIZE tokens and adds automated enforcement for imported Lucide icons.

The Babel-based scanner resolves supported imports and bindings. It excludes comments, strings, unrelated size props, accepted constants, shadowed tags, and metadata exports. Tests cover these rules.

The check runs through npm run check:icon-size and the CI typecheck job.

Source of truth

This PR extends the existing ICON_SIZE and --icon-* scale. It does not create a parallel icon-size system.

Scope and complexity

The call-site changes are small and coherent. The parser-based scanner adds necessary complexity because text matching cannot resolve imports or distinguish icon props from unrelated syntax.

No safe deletion or simplification is evident. Removing scanner cases or tests would weaken enforcement or regression coverage.

Risks and validation

  • Migrated values preserve the existing rendered sizes through shared constants.
  • New off-scale Lucide icon sizes can now fail CI.
  • Import resolution and configured dynamic-tag handling are the main maintenance areas.
  • Tests cover aliases, namespaces, derived imports, relative imports, conditional values, shadowed tags, unrelated props, comments, strings, numeric strings, and accepted constants.
  • Required-check status is unverified from the available evidence.

Review-relevant risks

The scanner changes repository governance by enforcing the icon-size scale in CI. Any material change to this governance behavior requires independent human review under repository policy.

No user-visible behavior, public contract, security, licensing, or release effect was identified in the current diff. The person performing the merge must review the final diff, and a maintainer makes the final determination.

Walkthrough

The pull request adds Babel-based validation for numeric sizes on imported Lucide icons. It replaces three hardcoded icon sizes with shared constants, adds scanner tests, and runs the new check in CI.

Changes

Icon size governance

Layer / File(s) Summary
AST icon-size scanner
scripts/check-icon-size.mjs
The scanner resolves supported icon imports, aliases, namespaces, dynamic tags, and lexical scopes. It reports numeric JSX size values.
Shared icon-size call sites
apps/desktop/src/renderer/settings/about-settings-page.tsx, apps/desktop/src/renderer/settings/request-customization-editor.tsx, packages/ui/stories/icons.stories.tsx
Selected icon sizes now use ICON_SIZE.plate, ICON_SIZE.chrome, and Icons.ICON_SIZE.empty.
Validation command and CI integration
scripts/check-icon-size.test.mjs, package.json, .github/workflows/ci.yml
Tests cover supported and excluded cases. The package script runs the tests and scanner. The typecheck job invokes the script, and @babel/parser is added as a development dependency.

Estimated code review effort: 4 (Complex) | ~30 minutes

Merge Risk: ⚪ Minimal · up to 9be1c

This change centralizes the remaining icon sizes and adds CI enforcement; no actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant CI
  participant checkIconSize
  participant SourceFiles
  participant BabelParser
  CI->>checkIconSize: run check:icon-size
  checkIconSize->>SourceFiles: scan configured source globs
  SourceFiles-->>checkIconSize: return source text
  checkIconSize->>BabelParser: parse TSX
  BabelParser-->>checkIconSize: return AST
  checkIconSize-->>CI: report violations or success
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR updates three governed call sites and adds enforcement, but it does not complete the issue's broader icon-size sweep or record exceptions. Complete the governed-scale sweep across the targeted UI and desktop call sites, or document the remaining scope and genuine exceptions.
Ai Use Disclosure ⚠️ Warning The PR description selects neither required disclosure, and all three introduced commits lack a valid Generated-by trailer. Update the PR description with exactly one disclosure. If generative tooling made a substantive contribution, name the tool and scope, add trailers to material AI-authored commits, and preserve them through squash or amend. See “Human ow...
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: governing remaining UI icon sizes with ICON_SIZE.
Description check ✅ Passed The description includes a summary, issue reference, and test commands, but omits the template's AI use and checklist sections.
Out of Scope Changes check ✅ Passed The CI, script, dependency, tests, and icon call-site changes directly support the linked issue's icon-size governance objectives.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: afd08f49-0d1a-411c-9cee-1adc4872f934

📥 Commits

Reviewing files that changed from the base of the PR and between 2e3c82e and b7bdeab.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • apps/desktop/src/renderer/settings/about-settings-page.tsx
  • apps/desktop/src/renderer/settings/request-customization-editor.tsx
  • package.json
  • packages/ui/stories/icons.stories.tsx
  • scripts/check-icon-size.mjs
  • scripts/check-icon-size.test.mjs

Included review availability: Your plan includes up to 3 reviews per rolling hour; 0 remain after this review.

Comment thread scripts/check-icon-size.mjs Outdated
Comment thread scripts/check-icon-size.mjs Outdated
Shadowed parameters and local tags are no longer treated as imported
icons. Numeric string sizes such as size="16" are rejected. The Comp
story is governed only when Comp is actually bound to the icon seam.
Biome wants the long boolean and defineBinding calls wrapped.
@Astro-Han

Copy link
Copy Markdown
Contributor

Thanks for routing the leftover sizes through ICON_SIZE — the two product mappings are correct and the check does run on real TSX. Two P2s surfaced in review; both need a decision before merge.

Conclusion: PASS with two mandatory-handling P2s.

P2-1 — the About hero change is a visual change, not a routing: size={30}ICON_SIZE.plate (28), and the sizing rationale comment was deleted.
about-settings-page.tsx:177 previously read size={30} /* 64% of the 48px plate, matching .providerLogo's fill */ and now reads size={ICON_SIZE.plate} (plate: 28). .settingsAboutLogo renders the glyph at its own pixel size, so this shrinks the Sparkles by 2px (62.5% → 58.3% of the plate) and silently abandons the 64% fill convention that .providerLogo still uses (models.css:68-71). The PR description presents this as a pure 1:1 route, which it isn't. Please either confirm the 28px convergence is intentional and say so explicitly in the description (keeping a note about the old 64% convention), or keep 30 as a documented exception per issue #2359's own rule.

P2-2 — the 282-line AST checker is oversized for the threat and has blind spots.
The entire governed corpus has exactly 3 raw-pixel size={N} hits — all three are the ones this PR fixes; there are no false-positive threats in the corpus today. Meanwhile the hand-written scope engine (~200 lines) replaces @babel/traverse (already in the dependency tree, same version as the added @babel/parser), and I verified the namespace/metadata machinery is provably dead (the only import * as Icons consumer is the story, whose tags are destructured bindings that the checker explicitly does not govern). Separately, 8 trivial indirection forms bypass the check entirely (verified): const size = 16; <Search size={size}/>, <Search {...{ size: 16 }}/>, <Search size={\16`}/>, <Search size={8 + 8}/>, <Search size={'16'}/>`, parameter passthrough, dynamic import, and a local re-export shim. So the docstring's claim ("Product lucide icons must pick a rung … not a raw pixel") only holds for literal direct writes. Please either (a) justify the full AST approach, (b) trim the provably dead machinery (~100 lines), or (c) state the coverage boundary explicitly and encode it in the tests.

Optional nits (P3): the story "governance" is nominal — the check can't see Comp (the 4th test explicitly asserts a destructured tag is not reported), so reverting the story to size={20} passes silently; GLOBS covers packages/ui/stories but not apps/desktop/stories (which imports the icon seam); template literals and size={'16'} are untested; the description's rungs ("chrome / dense / hero") don't match the code's (meta/control/chrome/empty/plate).


AI-assisted review disclosure: this review was produced with AI assistance (two pi review subagents on ollama-cloud/deepseek-v4-flash). Both independently reproduced the 30→28 pixel delta and the bypass matrix against the actual script; I verified the call-site values and CSS conventions. The "checker is dead machinery" claim is from corpus-wide analysis, not observed breakage. Please weigh these findings with your own judgment.

中文摘要(AI 辅助审查)

结论:PASS(两个 P2 需处理)。① P2-1:About hero 的 size={30}→ICON_SIZE.plate(28) 是未披露的视觉变更——Sparkles 缩小 2px(占板比例 62.5%→58.3%),且删除了"64% of 48px plate, matching providerLogo"的注释依据,而 providerLogo 仍用 64% 填充惯例;PR 描述暗示纯路由但实际改了像素。需明确确认收敛到 28 是有意的(并在描述中说明),或按 issue #2359 规则保留 30 作为记录在案的例外。② P2-2:282 行手写 AST lint 超配——被治理语料全仓仅 3 处裸像素(都是本 PR 修的),命名空间/元数据机制是可证明的死逻辑(约 100 行),且 8 种平凡间接形式(变量、spread、模板串、表达式、字符串容器、参数透传、动态 import、本地 re-export)实测均可绕过,治理声明被高估。建议:给全 AST 方案理由、删死逻辑、或明确声明覆盖边界并写进测试。P3 可选:story 治理是表面治理(解构标签不被查)、GLOBS 不含 apps/desktop/stories、模板字面量未测、描述档位名与代码不符。

@Astro-Han Astro-Han 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.

Routing the remaining product call sites through the existing ICON_SIZE seam is the right direction, and this head fixes the earlier shadowing and direct numeric-string false negatives. The focused tests and live checks are green on its old base.

The new required check does not pass the current-main merge result: current main added five governed 18px icons after this branch's base. Please rebase and migrate those sites before merging. More broadly, the first-principles contract should match what the checker can actually prove. The smallest coherent choice is either (a) explicitly enforce direct JSX literals and delete the unused/generalized scope machinery, or (b) expand static-value and controlled-derived-icon coverage. A 319-line authority that claims all raw pixels while accepting common indirections is the least desirable middle ground.

Reviewed with Codex using two independent reviewer agents; I reproduced the current-main gate failure and verified the latest head, changed checker, public discussion, and live CI.

中文

把剩余产品调用点接入现有 ICON_SIZE seam 的方向正确,这个 head 也修复了之前的遮蔽误报和直接数字字符串漏报;聚焦测试与旧 base 上的实时检查均通过。

但新增 required check 无法通过与当前 main 的合并结果:当前 main 在该分支 base 之后新增了 5 个受治理的 18px 图标。合并前应 rebase 并迁移这些调用点。更根本地说,checker 能证明的范围应与契约一致。最小的一致方案要么只明确治理直接 JSX literal,并删掉未产生价值的泛化 scope 逻辑;要么补齐静态值和受控派生图标的覆盖。319 行的 authority 一边声称禁止所有 raw pixel,一边允许常见间接写法,是最不理想的中间状态。

本次由 Codex 配合两个独立 reviewer agent 审查;我复现了 current-main gate 失败,并核验了最新 head、checker 变更、公开讨论和实时 CI。

Comment thread .github/workflows/ci.yml Outdated
- run: npm ci
- run: npm run lint
- run: npm run format:check
- run: npm run check:icon-size

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.

P1 — This required gate fails after merging current main. Current main added remote-project-directory-dialog.tsx after this branch's base, with five governed Lucide icons still using size={18} (lines 207, 209, 256, 277, 278). Applying this PR and running the scanner reports all five, so the merged result cannot pass this new CI step despite the old-base checks being green. Rebase, map those sites to the appropriate ICON_SIZE rung, and rerun this job.

!attribute.value
) {
return false;
}

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.

P2 — Make the stated governance boundary honest. The scanner still accepts common static raw-pixel forms such as size={'16'}, size={`16`}, size={8 + 8}, spreads, and prop passthrough; controlled <Comp> icons in the changed story are also outside the authority. Either narrow the contract to direct literals and simplify this 319-line implementation, or add explicit static-value/derived-icon coverage and fixtures.

@Astro-Han

Copy link
Copy Markdown
Contributor

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Dynamic icons escape governance 🐞 Bug ⚙ Maintainability
Description
Fix-now: callback parameters are always bound as non-icons, so the Comp produced from
Object.entries(Icons) is not recognized and changing the story back to <Comp size={20}> produces
no violation. The CI check therefore does not enforce the exact story call site this PR claims to
govern.
Code

scripts/check-icon-size.mjs[R151-155]

+function bindParams(scope, node) {
+  const params = node.params ?? [];
+  for (const param of params) {
+    for (const name of patternNames(param)) defineBinding(scope, name, 'not-icon');
+  }
Relevance

●●● Strong

Accepted precedents favor fixing concrete script enforcement gaps, especially parser failures that
omit required diagnostics or checks.

PR-#3188
PR-#3154

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The story builds LUCIDE_ICONS from the imported icon namespace and destructures each entry into
the callback parameter Comp, which is rendered as the icon. The checker nevertheless assigns all
callback parameters the not-icon binding, while governed JSX tags must resolve specifically to
icon; therefore a numeric size on this Comp is skipped.

packages/ui/stories/icons.stories.tsx[32-34]
packages/ui/stories/icons.stories.tsx[57-70]
scripts/check-icon-size.mjs[151-155]
scripts/check-icon-size.mjs[205-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The icon-size checker treats every function or callback parameter as `not-icon`. Consequently, the `Comp` parameter in the icon story's `LUCIDE_ICONS.map(...)` callback is disconnected from the icon namespace that populated the collection, allowing raw numeric sizes to pass CI.

## Issue Context
The story intentionally derives its icon list from `Object.entries(Icons)` so it remains synchronized with the centralized icon export seam. Deleting that dynamic path or replacing it with a manually maintained list would duplicate authority; reuse the existing scope/binding analyzer by propagating known icon provenance through the collection and destructured callback binding. Add regression coverage for the actual story pattern; this adds limited data-flow state and corresponding test maintenance because the existing direct-binding model cannot represent this provenance.

## Fix Focus Areas
- scripts/check-icon-size.mjs[151-155]
- scripts/check-icon-size.mjs[251-262]
- scripts/check-icon-size.test.mjs[11-26]
- packages/ui/stories/icons.stories.tsx[32-33]
- packages/ui/stories/icons.stories.tsx[57-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This adds a runtime-enforced CI check with substantial custom AST/scoping logic and several icon call-site changes, creating real correctness and maintenance risk, but it remains one localized concern rather than a bug-dense multi-path change warranting redundant review.

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scripts/check-icon-size.mjs Outdated
Comment on lines +151 to +155
function bindParams(scope, node) {
const params = node.params ?? [];
for (const param of params) {
for (const name of patternNames(param)) defineBinding(scope, name, 'not-icon');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Dynamic icons escape governance 🐞 Bug ⚙ Maintainability

Fix-now: callback parameters are always bound as non-icons, so the Comp produced from
Object.entries(Icons) is not recognized and changing the story back to <Comp size={20}> produces
no violation. The CI check therefore does not enforce the exact story call site this PR claims to
govern.
Agent Prompt
## Issue description
The icon-size checker treats every function or callback parameter as `not-icon`. Consequently, the `Comp` parameter in the icon story's `LUCIDE_ICONS.map(...)` callback is disconnected from the icon namespace that populated the collection, allowing raw numeric sizes to pass CI.

## Issue Context
The story intentionally derives its icon list from `Object.entries(Icons)` so it remains synchronized with the centralized icon export seam. Deleting that dynamic path or replacing it with a manually maintained list would duplicate authority; reuse the existing scope/binding analyzer by propagating known icon provenance through the collection and destructured callback binding. Add regression coverage for the actual story pattern; this adds limited data-flow state and corresponding test maintenance because the existing direct-binding model cannot represent this provenance.

## Fix Focus Areas
- scripts/check-icon-size.mjs[151-155]
- scripts/check-icon-size.mjs[251-262]
- scripts/check-icon-size.test.mjs[11-26]
- packages/ui/stories/icons.stories.tsx[32-33]
- packages/ui/stories/icons.stories.tsx[57-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@Astro-Han

Copy link
Copy Markdown
Contributor

This PR changes icon sizing in visible Settings surfaces. Could you please add screenshots confirming the resulting About hero icon and request-header delete icon? A compact before/after composite is fine. Thanks!

Posted by Codex on behalf of Astro-Han.

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.

Converge icon sizes onto a governed scale

2 participants