refactor(loading): unify every placeholder on one shared skeleton primitive - #1281
Open
Harry19081 wants to merge 1 commit into
Open
refactor(loading): unify every placeholder on one shared skeleton primitive#1281Harry19081 wants to merge 1 commit into
Harry19081 wants to merge 1 commit into
Conversation
…mitive PR #1280 landed the static-placeholder primitive as `Ghost`/`GhostBar` and converted the GitHub detail and inbox/list surfaces. Two gaps remained. The name collides. `Ghost` is already taken twice in this repository — `GHOST_PILL_*` in `PillGroup` for a low-emphasis pill surface, and `ReferenceDragGhost` for a drag preview — while "skeleton" is already this repo's word for exactly this pattern (`GitHubDetailSkeleton`, `TimelineLoadingSkeleton`, the `detail-tab-count-skeleton` test id). Rename the module and its list wrapper to `Skeleton` / `SkeletonBar` and `ListPanelSkeletonRows` before the name spreads further. Six hand-rolled placeholders were left behind. They still animated with animate-pulse and disagreed on their fill token, three using `bg-fill-3` where seven used `bg-fill-2` for the same role. Convert the five live ones to `SkeletonBar` and unify the fill. Delete `McpTableSkeleton` rather than convert it: it was exported from `McpTableParts` and imported by nobody, so it rendered nowhere. Add a `testId` passthrough to `SkeletonBar` so `ChatLoadingBlock` keeps the `chat-loading-block` test id that three suites assert, without wrapping the primitive in a div that exists only to carry an attribute.
Harry19081
force-pushed
the
refactor/loading-skeleton-sweep
branch
from
September 4, 2026 19:22
ed468f1 to
5566106
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
#1280 landed the static-placeholder primitive and converted the GitHub detail
and inbox/list surfaces. Two gaps remained.
The primitive's name collides. #1280 shipped it as
Ghost/GhostBar.Ghostis already taken twice in this repository —GHOST_PILL_*inPillGroupfor a low-emphasis pill surface, andReferenceDragGhostfor a dragpreview — while skeleton is already this repo's word for exactly this pattern
(
GitHubDetailSkeleton,TimelineLoadingSkeleton,McpTableSkeleton, thedetail-tab-count-skeletontest id). The rename was meant to be part of #1280,but that PR was merged before the amend landed, so it is folded in here — while
the primitive still has only three import sites rather than nine.
Six hand-rolled placeholders were left behind, and they disagreed with each
other on two axes:
animate-pulse, so the flash thatfix(loading): replace pulsing skeletons with static skeleton rows #1280 removed from the GitHub surfaces was still present in chat, the canvas
inline card, two Integrations panels, and the DevTools playground.
bg-fill-3and three usedbg-fill-2for the same semantic role — a placeholder for content that hasnot arrived.
fill-3is one step darker in both themes (#e3e3e3vs#efefeflight,#2e2e30vs#212121dark).Separately, the audit found that one of the six was never on screen at all:
McpTableSkeletonis exported fromMcpTableParts.tsxand the identifierappears nowhere else in
src/. Both modules that import from that file(
McpTable.tsx:46,AgentMcpSection.tsx:39) pull other members.Solution
Rename the primitive, then converge every remaining placeholder on it. One
responsibility: exactly one loading-placeholder primitive exists, it is named
the repository's own word for the pattern, and every placeholder uses it.
src/components/Ghost/→src/components/Skeleton/(GhostBar→SkeletonBar), andListPanelGhostItem.tsx→ListPanelSkeletonRows.tsx(
ListPanelGhostList→ListPanelSkeletonRows, test idslist-panel-ghost-*→list-panel-skeleton-*). Rename-only across the fiveconsumers #1280 touched; no behavior change in that half of the diff.
Then convert the five live placeholders to
SkeletonBarand delete the deadone.
src/components/ChatLoadingBlock.tsxSkeletonBar, keeps its 900px width tokensrc/engines/ChatPanel/blocks/primitives/ChatLoadingBlock.tsxSkeletonBar, keeps its 800px width tokensrc/engines/ChatPanel/blocks/CanvasInlineCard/index.tsxSkeletonBar,fill-3→fill-2src/modules/MainApp/Integrations/RulesMemoryEvolution/Evolution/AgentEvolutionPanel.tsxSkeletonBar,fill-3→fill-2src/modules/MainApp/Integrations/DevTools/playground/previews/EventRenderer.tsxSkeletonBarsrc/modules/MainApp/Integrations/Mcp/Table/McpTableParts.tsxSKELETON_ROW_COUNTand empty section bannerSkeletonBargains one prop:testId.ChatLoadingBlockmust keep thechat-loading-blocktest id that three suites assert, and threading it throughthe primitive avoids wrapping it in a
<div>whose only job is to carry anattribute.
On the
fill-3→fill-2unification: there is no evidence the darker valuewas deliberate.
AgentEvolutionPanel's placeholder stands in for aSwitch,and
Switchstyles its track through the SCSS class.switch-track, not aTailwind fill token — so the placeholder was never matched to the control's
real color to begin with. Seven sites already used
fill-2.Resulting invariant, now repository-wide: a loading placeholder is a static
SkeletonBarfilled withbg-fill-2. Animated indicators — spinners,streaming dots, the CI-pending heartbeat — are a different affordance and are
explicitly out of scope;
docs/frontend-ui-audit-2026-09-05/GLOBAL.mdlistseach one as
keep with reasonso the next audit pass does not re-flag them.Potential risks
fill-3→fill-2change isthe only user-visible delta beyond the removed animation, and it is one step
lighter in both themes. If a reviewer decides the canvas card wanted more
contrast against its surface, the revert is a
classNameon those five bars —but note that reintroducing it restores the drift this PR removes.
McpTablenow has no loading placeholder in the source at all. That isnot a behavior change — the deleted component rendered nowhere — but anyone
who assumed the table had a loading state will find it does not. Giving it one
is a feature, not part of this sweep.
ChatLoadingBlockrenders a<span>instead of a<div>.SkeletonBaris a span so it stays valid inside phrasing containers. Both blocks sit in
block context where
display:blockon a span is equivalent, and both suiteswere updated from a
<div>regex to a<span>one. A caller relying ondivin a CSS selector would break; none do.testIdwidens the primitive's API. It renders as an omitted attributewhen unset, so no existing markup changes.
ChatLoadingBlockvariants are still two components. Not mergedon purpose — see the
watchrow indocs/frontend-ui-audit-2026-09-05/ChatLoadingBlock.md.The 900px/800px split is documented and deliberate (
detailPanelTokens.ts:75),two occurrences is below the 3+ abstract threshold, and merging costs ~12
import-site edits.
(identifier and test-id substitution) and the full suite plus
tsgocover it,but it does mean this diff is larger than the sweep alone. Reviewing the
rename half by identifier is faster than by hunk.
first pass rewrote an unrelated
appearance="ghost"Button prop inActivityTimelinetoappearance="skeleton".tsgocaught it and it wasrestored; it never reached
develop, anddevelop's copy is verified intact.Every remaining occurrence of "skeleton" in the touched files was then read
back by hand.
Verification
npx vitest run --config config/vitest.config.ts— 1489 of 1490 files and11200 of 11202 tests passed. The two failures are
src/modules/WorkStation/shared/StatusBar/__tests__/CiStatusMenu.test.ts,which is not part of this PR:
CiStatusMenu.tsxis modified in the sharedlocal checkout by a concurrent session, the file has zero overlap with
anything changed here, and the suite passes in isolation
(
npx vitest run --config config/vitest.config.ts CiStatusMenu→ 2/2). CI onthis branch runs against the committed version of that file.
npx tsgo --noEmit --pretty false— clean, exit 0. It caught a real defectmid-change: a blanket
ghost→skeletonrename in fix(loading): replace pulsing skeletons with static skeleton rows #1280 had clobbered anunrelated
appearance="ghost"Button prop inActivityTimeline. Restored.npx oxlint -c .oxlintrc.json --max-warnings 0over every touched directory —clean.
npx prettier --checkon all changed files — clean.ChatLoadingBlock(both),ActivityRouter,ActivityRouter.canvasInline,ChatTranscript,CanvasInlineCard(13 suites),ListPanelSkeletonRows.ChatLoadingBlocksuites were rewritten to assert the absence ofanimate-pulse, the retained width token, and the retained test id.git show origin/develop:src/modules/shared/components/ActivityTimeline/index.tsx— confirmed
appearance="ghost"is intact ondevelop, i.e. the substitutionslip described above never shipped.
Not run: no visual evidence. This is a Tauri desktop app and screen capture is
denied to the agent on this machine. The
fill-3→fill-2delta is the onechange a screenshot would have settled, so exact values for both themes are
given under
Potential risksfor a reviewer to judge, and the swept surfaces(chat transcript, canvas card, Integrations → Rules/Memory, MCP table) are worth
one pass by eye.
Audit
frontend-ui-auditrun over the changed files per.claude/CLAUDE.mdrouting;reports in
docs/frontend-ui-audit-2026-09-05/(GLOBAL.mdplus one file percomponent).
Verdict totals: 7 fix, 6 keep with reason, 1 abstract (landed),
2 watch.
All 7 fixes are in this PR. Deferred, with reasons recorded in the reports:
CanvasInlineCard/index.tsx:59has a hardcoded Englisharia-label="Loading canvas content"— a real finding, but an i18n change needing a key across 14locales, which is a different responsibility from a placeholder sweep.
MarkdownLocalImage.tsx:219andChatImageThumbnail/index.tsx:118arecharacter-identical pulsing image icons —
watch, pending a third occurrence.The reports are included here rather than split into a docs-only PR because they
explain this PR's single change and nothing else.
🤖 Generated with Claude Code