Repository navigation
feat(marketplace): port official 0.78.1 manage page (管理插件和技能) - #26
Conversation
The browse dialog's 已连接 N 个 entry opens a second-level page. Upstream 0.18 renders that page differently, so this ports official 0.78.1's detail bar, hero, installed rows and skills section, and re-recipes the four shared components official uses on both pages. Three differences are fixed. The green 已连接 badge rendered white because the injector looked for a class token anywhere in the stylesheet instead of inside the dialog. The auth rows read 身份验证 because upstream hardcodes the authorize-mode labels. The detail-bar title stayed visible where official hides it until the hero scrolls out. The recipe fixture is captured live from official. The CSS companion holds official's rules verbatim.
Documents where each behaviour comes from in official's bytes, the four defects found against official, the measured parity, and the pixel delta with its two sources.
e6b4f92 to
ea1af35
Compare
Official 0.78.1 ships three status-badge variants — connected, warn and error — and each carries its own colour class. The capture only ever saw the connected variant, so the companion CSS lifted one rule of the three and the warning and error badges shipped colourless. The guard now walks all three variants instead of the connected one alone, and a second guard asserts that every token the ported rules read is declared on the dialog, because an undeclared var() is invalid at computed-value time and the rule falls back to its initial value without failing anything else. The status variant classes move into one exported constant so the patch's replacement text and the guard read the same source. Fails on the current tree: "the companion CSS must lift official's warn badge rule".
Official 0.78.1 ships three status-badge variants — connected, warn and error — and each carries its own colour class. The capture only ever saw the connected variant, so the companion CSS lifted one rule of the three: the warning and the error badge resolved nowhere inside the plugins dialog and rendered with the inherited text colour. The companion now lifts official's warning and error rules verbatim, and the dialog's theme tokens declare the two colours those rules read. Upstream 0.18's palette defines neither, and the 0.75.5 group-author CSS declares both under .rg0755-colors, a scope the dialog is not inside. The three variant classes move into the ported-class set, so the build now fails closed when one of them resolves nowhere instead of shipping a colourless badge. Official's values: light #aa6300 and #cd2834, dark #e28600 and #ff6463. The light column is official's own :root,.sand-1vbcnu7 palette block, the dark column is the running official app's cursor-dark theme.
The second-level page's Installed section is the 0.75.0 wave's host for the repo's "已安装 / 卸载" panel and the custom-MCP form. Official 0.78.1's second-level page carries neither, and official keeps the connector form on a page of its own (qLe/7o, chunk-user-custom-connector-M5UeTEiw.js). The panel is therefore what made this reconstruction's page differ from official's. It also broke the build. The second-level port rewrites the section's children from J.map to (RM0781ManageRowElements=U.map, so the host's anchor no longer occurred and the pack step failed with "Expected one settings panel and one plugins list for the 0.75 hosts". The plugins host goes away. The custom-MCP form moves to the settings host slot, which is a reconstruction surface rather than an official-parity one. The uninstall action is not lost: the plugin detail page upstream already ships carries 卸载, and the desktop plugins controller keeps its own removal path. The content is taken from the sibling session's working tree, which had already made this change.
The guard read the whole token stylesheet, so a token declared only in the dark block counted as declared and the light theme fell back to the initial value without anything going red. Dropping the light yellow token survived the mutation check because the dark one was still there. The guard now reads the light block and the dark block separately: a token the ported rules read must be declared in the light block, and a token whose official value differs between the two themes must be declared in the dark block too. The port record gains the badge-colour measurements on both running apps and the 0.75 host removal.
|
PASS+NOTES Verified at head 1.
|
| token | light | dark |
|---|---|---|
--sand-text-yellow-primary |
#aa6300 |
#e28600 |
--sand-text-red-primary |
#cd2834 |
#ff6463 |
--sand-text-green-primary |
#00854a |
#00ba69 |
Cross-checked against official's own bytes, not the prose:
- official's light palette
:root,.sand-1vbcnu7inrea-grokbot-0781/app/dist/renderer/assets/index-Dh7EWA8t.cssdeclares--sand-text-yellow-primary:#aa6300,--sand-text-red-primary:#cd2834,--sand-text-green-primary:#00854a- exact match. - official's three rules appear there byte-for-byte, e.g.
.sand-132xtu3:not(#\#):not(#\#):not(#\#){color:var(--sand-text-yellow-primary)}. The port's rule text is official's; the only addition is the:where(.sand-plugins-dialog)scope prefix, and:where()contributes no specificity, so the cascade is unchanged. - the dark pair is official's palette scale:
yellow/3=#aa6300(light) /#e28600(dark) andred/3=#cd2834(light) /#ff6463(dark), inindex.eager-common-DbK3Oi_r.jsandapp/dist/electron-main/main-core.cjs. - official's registry chunk
index.eager-app-D1juOWZp.jsnames the same three classes:statusConnected:{kMwMTN:"sand-1ufr4hg"},statusWarn:{kMwMTN:"sand-132xtu3"},statusError:{kMwMTN:"sand-wtpwev"}- identical toMARKETPLACE_MANAGE_PAGE_0781_STATUS_VARIANT_CLASSESand to the built chunk. - the docs' colour table converts exactly:
#00ba69to rgb(0, 186, 105),#e28600to rgb(226, 134, 0),#ff6463to rgb(255, 100, 99).
All 42 rules in scripts/lib/marketplace-manage-page-0781-official.css were independently checked to be byte-verbatim lifts of official's rules (selector minus the scope prefix + declaration body), including the two @container sand-plugin-rows (width<560px) rules (official splits them across two identical blocks; the port merges them - equivalent) and the @media (prefers-reduced-motion:reduce) rule.
4. The 0.75 plugins host is gone, the form moved, uninstall survives - PASS
RPort0750PluginsPack: 0 occurrences in every built chunk, includingview-B5Ug8wEm.js; the old anchorlabelledBy:w,children:J.mapis gone from the plugins chunk too.- the settings chunk
index-BlqerJhg.jsstill mounts the custom-MCP form (MCP Servers,sand-plugins-port-0750,RPort0750SettingsPackpresent). bridge.mcp.uninstallPluginsurvives atfrontend/src/recovered/features/plugins/overlay/desktop.ts:561, on a live path (removePluginBrowserItemcalled fromdesktop-surface.tsx:264), not merely declared.
5. The guards bite - PASS
Each mutant applied alone, then restored from a distinct backup; worktree re-verified clean afterwards.
| mutant | expected | observed |
|---|---|---|
remove the sand-132xtu3 rule line from marketplace-manage-page-0781-official.css |
"must lift official's warn badge rule" | AssertionError: the companion CSS must lift official's warn badge rule - 1 fail, exit 1 |
remove --sand-text-yellow-primary:#aa6300; from marketplace-dialog-0781-theme-tokens.css |
a message about the light block | AssertionError: these tokens are read by the ported rules but not declared in the dialog's light block..., actual ['--sand-text-yellow-primary'] - 1 fail, exit 1 |
remove the 3-line MARKETPLACE_MANAGE_PAGE_0781_STATUS_VARIANT_CLASSES loop from marketplace-manage-page-0781-css-patch.mjs |
the test must fail again | Error: Marketplace manage-page CSS patch: the companion CSS carries rules for classes the port does not apply: sand-132xtu3, sand-wtpwev. - 1 fail, exit 1 |
bonus (the doc claims a 4th) remove the dark --sand-text-yellow-primary:#e28600; |
dark-block message | AssertionError: these tokens differ between official's two themes, so the dialog's dark block must declare them... - 1 fail, exit 1 |
6. Nothing unrelated rode along - PASS
git diff 6333d5bf2a2117b988dcf0454b9019bdca56ee50..0b548c224bef19619f21ec9d6e1e09f2d131fd2b --stat = 15 files, exactly the 9 the port owns plus the 6 0.75 host files:
docs/MARKETPLACE-0781-PORT.md +221
frontend/src/extensions/port-0750-packaged-hosts.tsx +15/-22
frontend/src/recovered/features/plugins/overlay/desktop-surface.tsx -5
frontend/src/recovered/features/plugins/overlay/port-0750-extras.tsx +18/-13
scripts/clean-build.mjs +13
scripts/lib/marketplace-dialog-0781-theme-tokens.css +8/-2
scripts/lib/marketplace-manage-page-0781-css-patch.mjs +619
scripts/lib/marketplace-manage-page-0781-official.css +118
scripts/lib/marketplace-manage-page-0781-patch.mjs +758
scripts/lib/port-0750-renderer-patch.mjs +13/-20
tests/fixtures/official-0781-manage-page-recipes.json +109
tests/fixtures/official-0781-marketplace-theme-tokens.json +8
tests/marketplace-manage-page-0781.test.mjs +411
tests/plugins-port-0750-extras.test.mjs +4/-14
tests/port-0750-renderer-patch.test.mjs +10/-30
Nothing else. The evidence chain also holds up: every official chunk the port cites exists in rea-grokbot-0781, and official's message ids iSLIjg/gcoiFh/6gRgw8 carry Connect/连接, Reconnect/重新连接, Retry/重试 in index.eager-common-DbK3Oi_r.js and chunk-core-CXBwRBzw.js, with official's K chain (idle to iSLIjg, waiting to gcoiFh, else 6gRgw8) in chunk-plugin-detail-sharepoint-write-MAPmM3e9.js matching what the port reproduces. All 573 class tokens in the recipes fixture trace to official's own assets (the single apparent exception, ui-dialog--xxl, is official's runtime-built ui-dialog--${size} modifier).
NOTE (low severity - cosmetic, keyboard-focus only): one of the 42 lifted rules does not ship
The build appends "23 of 42"; the other 19 are skipped because the class already resolves inside the dialog, and the patch's rule is "the class is the identity, not the rule text". For 18 of those 19, the rule that already applies is byte-identical to official's. One is not:
- port / official:
:where(.sand-plugins-dialog) .sand-1wpm9rq:focus-visible:not(#\#):not(#\#):not(#\#){outline-color:var(--sand-border-focus-primary)} - what actually applies (pre-existing, unscoped, from the repo's 0.75.8 info-pane CSS):
.sand-1wpm9rq:focus-visible:not(#\#):not(#\#):not(#\#){outline-color:var(--sand-border-focus)}
On the dialog subtree --sand-border-focus is initial - deliberately, because official does not define it - so outline-color is invalid at computed-value time and falls back to currentcolor: a visible focus ring where official renders transparent (official forces --sand-border-focus-primary: transparent !important under :root:not([data-sand-keyboard-nav])) or #1e86fc with keyboard nav on.
Impact: the :focus-visible outline colour of the detail bar's back button on the plugins dialog's second-level page, keyboard users only. It does not touch the three badge rules, the token declarations, the 0.75 host removal, or anything else in the port, and it is not a regression introduced here - the unscoped repo rule predates this PR; the port simply declines to lift official's rule on top of it.
Worth noting the companion CSS's header comment asserts the 42 "do not resolve inside the dialog", which is not true for sand-1wpm9rq, and the PR's guard only asserts "the rule exists in official's stylesheet" plus "the class resolves in-dialog", so this substitution is not covered. A guard asserting the lifted rule text itself reaches the staged stylesheet would close it.
The independent verifier found that the second-level page's back button draws a
pale ring on keyboard focus where official draws a blue one, and measured it on
both running apps: official rgb(24, 132, 252), this build rgba(245, 245, 245,
0.66), both 2 px solid.
The cause is two earlier waves, not this port: the app-link-card port writes an
unscoped `.sand-1wpm9rq:focus-visible{outline-color:var(--sand-border-focus)}`,
and the dialog resets `--sand-border-focus` to `initial` because official does
not define it. Official's own rule for that class is skipped by the
marketplace companions, because the app-link rule makes the class look already
resolved.
Official's blue reaches the button through a `[data-sand-tone="dark"]` subtree
in its runtime theme stylesheet, while the dialog element itself computes
`transparent`, so declaring the blue on the dialog would make every ring in the
dialog blue instead of only that subtree's. That is its own wave.
Why
Official 0.78.1 opens a second-level page from the browse dialog's 已连接 N 个 entry. This build renders that page with upstream 0.18's layout and styling. This port brings official's page over and fixes the differences measured against the running official app.
What changed
marketplace-manage-page-0781-patch.mjs, which rewrites upstream's detail bar, hero, installed rows and skills section to official's own components. It also re-recipes the shared row, grid, section and show-all components that official uses on both pages.marketplace-manage-page-0781-css-patch.mjsandmarketplace-manage-page-0781-official.css. The companion holds 42 rules copied verbatim from official's stylesheet, and the injector scopes every one of them to the plugins dialog.Scope
This covers the manage page and the four shared components it re-recipes. It leaves the shared typography primitives alone, because official's differ from upstream's across the whole app. It also moves one repo feature out of the dialog, into the settings host.
Tradeoffs
The page is re-recipeed from upstream's own components rather than rewritten. Official's component tree would copy more bytes and would need its own state plumbing, while the recipes are the part that differs in the pixels.
The uninstall action is not lost with the panel. The plugin detail page upstream already ships carries 卸载, and the desktop plugins controller keeps its own removal path.
Blast Radius
The patch rewrites
view-B5Ug8wEm.jsat build time and adds rules to the renderer stylesheet. The four re-recipes reach the browse page's own rows, which matches official, since it shares those recipes across both pages. The 0.75 host removal reaches the dialog's Installed section only; the settings host gains the custom-MCP form it did not have.Verification
npm run checkin a clean worktree gives 2694 pass, 0 fail and 34 skipped.rgb(0, 186, 105), warnrgb(226, 134, 0), errorrgb(255, 100, 99).app.asarsha256b2803efb37b4fd42f9292520153550236cf9fcfc2c8dc10f551c09f6d272272bwas deployed to/Applications/Grok Node.app, and the deployed asar's sha256 equals the freshly built asar's.