Repository navigation
Commit 677ac3b
fix(app-shell): a marketplace README fits its detail page; badges render as images, tables as tables (#30426)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgRknVqcGH2U7KEF3fxmBr1v)_
domain:ui#2 · os-bill · session_01Utp6Cb5R6JDdfyvtj3dPR2
Fixes #30330
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:商店详情页的 README
不再被截掉:长行自动换行,只有代码块和表格在自己框内横向滚动。徽章显示成图片,表格显示成表格,`---` 显示成分隔线,不再露出
Markdown 原文。
- 为什么改:HotCRM 详情页(883 px 窗口)里一条 259 字符的代码行把整个 README 撑到 1926
px,右边被裁掉、无法滚动;徽章行显示成 ` …` 字面。README
是应用的销售页,半截句子和原始标记让成品像半成品。
- 风险与回滚:只动 app-shell 商店目录两处(README 渲染器 `MarkdownText`、详情页两列加
`min-w-0`)及测试与 changeset,不动 spec、服务端与共享内联渲染器。图片只认绝对 `https:` 地址并以
no-referrer 加载,其余来源整段略去;控制台 CSP 本就允许 `img-src https:`。回滚即 revert 本 PR。
-
先看:`ui/packages/app-shell/src/console/marketplace/MarkdownText.tsx`(`renderInline`
的图片切分与 `absoluteUrl`、表格块规则)→ `MarketplacePackagePage.tsx`(两个网格单元的
`min-w-0`)→ 测试 `MarkdownText.wideReadme-30330.test.tsx`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/console/marketplace/__tests__/MarkdownText.wideReadme-30330.test.tsx
packages/app-shell/src/console/marketplace/__tests__/MarketplacePackagePage.readmeWidth-30330.test.tsx`,预期全部通过;在
main 的代码上同一命令 26 条红、5 条对照绿。
- 需要你做的:无,席位落地。
## What changes
**Which element forced the width (the card left it open).** Below the
`lg` breakpoint the detail page's `grid gap-4 lg:grid-cols-3` has one
implicit `auto` column, and a grid item's automatic minimum width is its
content's min-content width. HotCRM's README has a fenced `curl … -d
'{"jsonrpc":…}'` line of 259 characters; a `pre` does not wrap, so its
min-content width is that line, and it sized the column, the About card
and the README at about 1926 px. The `pre` already had
`overflow-x-auto`, but it never became narrower than its line, so it
never scrolled. (At `lg` and up the template is `repeat(3, minmax(0,
1fr))`, which has no automatic minimum, so the defect showed only on
narrower windows.)
- **`MarketplacePackagePage.tsx`.** Both grid cells (README and
Versions) carry `min-w-0`, so the column keeps the page's width and the
fence scrolls inside itself.
- **`MarkdownText` root.** `min-w-0 break-words`, so a long unbroken
word (a URL, a long code span) wraps instead of overflowing; a list
item's text span gets `min-w-0` beside its bullet for the same reason. A
`pre` keeps `white-space: pre` and its own scroller; a table scrolls in
its own `overflow-x-auto` box.
**Why the badges printed as markup.** HotCRM writes
`[](LICENSE)`. The renderer had
no image rule, and the shared inline link rule read
`[` as a link whose text is `` as text, which reads exactly as the card measured.
- **Images.** `MarkdownText` now matches `` and the
linked form `[](href)` before handing the rest of the text to
`@object-ui/components`' `renderInlineMarkdown` (unchanged; it is shared
with `element:text` and outside this card's surface). An image renders
only from an absolute `https:` URL, with `referrerPolicy="no-referrer"`,
`loading="lazy"` and `max-w-full`; any other source (a
repository-relative path, `/…`, `//…`, `http:`, `data:`, `javascript:`,
any whitespace or control character) is left out, alt text included. A
linked image keeps its anchor only for an absolute `http(s):`
destination (new tab, `noopener noreferrer`, the README link look): a
relative destination such as `LICENSE` would resolve against the
console, not the repository. A code span is matched first, so ``
`` `` stays code. No fetch path is invented for relative sources.
- **Tables.** The card's Expected names wide tables, and HotCRM's README
has five pipe tables that printed as one long line of pipes. A GFM pipe
table (a line with a pipe above a delimiter row with the same cell
count, then rows up to a blank line or another block) renders as a
`table` with the delimiter row's alignment, cells drawn by the same
inline pass, short rows padded and extra cells dropped, `\|` read as a
literal pipe. A pipe in prose with no delimiter row stays text.
- **Thematic breaks (bounded in-place fix, same class: Markdown printed
as literal text).** `---`, `***`, `___` (spaces allowed) between blocks
render as `hr`; `* * *` is a rule, not a list item. A `---` line
directly under a paragraph line stays that paragraph's text, as before
(CommonMark reads it as a setext underline, which this renderer does not
draw). HotCRM's README has four such rules that printed as `---`.
The file header's "If a README really needs tables or images, the
publisher can host their own docs" is replaced by the rules above. No
raw HTML is rendered and no dependency is added.
## Tests
- `MarkdownText.wideReadme-30330.test.tsx` (new): HotCRM's badge row
renders four `img` and no ` in a
paragraph and in a list item; a refused link scheme keeps the image
without the anchor; an image title, and image syntax inside a code span
staying code; tables (cells, inline marks, escaped pipe, alignment, own
scroller, padding/truncation, HotCRM's screenshot table); five
thematic-break spellings; the compact size; the width classes. CONTROL:
headings, links, lists, quotes and fences render as before, a pipe in
prose stays text, a `---` under a paragraph line stays text, a relative
text link keeps its `href`.
- `MarketplacePackagePage.readmeWidth-30330.test.tsx` (new, the
`releaseNotes-28891` harness): both grid cells carry `min-w-0`; CONTROL:
the README fence renders in the About cell with its own scroller, and
the Versions notes render.
Runs (all under `scripts/pm/os-verify-lock.sh`, `pnpm --dir ui exec
vitest run …`):
- Red: the two new files against `origin/main`'s `MarkdownText.tsx` and
`MarketplacePackagePage.tsx` (checked out over the fix, then restored
with `git checkout HEAD --`, `git diff HEAD` empty): `Tests 26 failed |
5 passed (31)`, the 5 being the CONTROL rows.
- Green, after the fix: the two new files plus every `MarkdownText.*`
and `MarketplacePackagePage*` test in the directory (21 files): `Test
Files 21 passed (21)`, `Tests 159 passed (159)`. On the final head
`6a86665c`: the 7 files that render `MarkdownText` plus
`scripts/__tests__/markdown-test-inputs.test.ts`: `Tests 80 passed
(80)`.
- Type-check: `pnpm exec turbo run type-check
--filter=@object-ui/app-shell --concurrency=2`: `Tasks: 68 successful,
68 total`; on the final head, `pnpm --filter @object-ui/app-shell
type-check`: `VERDICT command-exit 0`. Lint: `eslint` on the touched
files: 0 errors (the warnings are pre-existing `no-explicit-any` in
`MarketplacePackagePage.tsx`).
The whole change is the ablation: the red run above is the new pins
against the old code, each flipped by the code it names.
<details><summary>Real-browser width measurement (Chromium, 883 px
viewport)</summary>
jsdom does no layout, so the tests pin the classes. The mechanism was
measured in Chromium with a static page whose CSS mirrors the Tailwind
classes (an 835 px `overflow: hidden` parent as the card measured, a
one-column grid, the card's 24 px padding, the README's flex column,
HotCRM's 259-character fence line at 12 px monospace, and a
200-character unbroken URL in a paragraph):
- Before (no `min-width: 0`, no `overflow-wrap`): parent `scrollWidth
1947` in `clientWidth 835`; card `1945` wide; `pre` `1895` wide with
nothing to scroll; the paragraph's right edge at `1922` px.
- After (`min-width: 0` on the grid cell and the README root,
`overflow-wrap: break-word`): parent `835`/`835`; card `833`; `pre`
`clientWidth 783`, `scrollWidth 1895` (scrolls inside itself); the
paragraph's right edge at `810` px.
The measurement page is a reconstruction, not the Console build; the
Console's own render on a live stack was not exercised.
</details>
## Acceptance notes
- `ui/AGENTS.md` #2 (docs-driven): no page under `content/docs/`
describes which Markdown a marketplace README may use
(`deployment/marketplace-handbook.mdx` names the `readmePath` and
`readme` fields only), so no doc changes; that tree is also outside this
card's surface.
- A text link to a relative path (`[the guide](docs/guide)`) still
resolves against the console, as before; the shared inline pass owns
that rule and `element:text` reads it too, so it is untouched here.
Observation, not filed.
- An image nested inside a text link's label (`[see ](z)`) is not
a shape this renderer reads; the image renders or drops and the link's
brackets print. Not seen in any measured README.
## Verification
<details><summary>Derived gates (dispatch-gates --commands on merge head
d13ed5b; re-runs on final head 6a86665): 35 run, all exit 0</summary>
- `node scripts/check-adr-0087-registration.mjs --base origin/main` ::
exit 0
- `node scripts/check-adr-0087-registration.mjs --self-test` :: exit 0
- `node scripts/check-changeset-no-major.mjs --base origin/main` :: exit
0
- `node scripts/check-closing-keyword-parity.mjs` :: exit 0
- `node scripts/check-closing-keyword-parity.mjs --self-test` :: exit 0
- `node scripts/check-comment-mask-corpus.mjs` :: exit 0
- `node scripts/check-empty-changeset.mjs --base origin/main` :: exit 0
- `node scripts/check-empty-changeset.mjs --self-test` :: exit 0
- `node scripts/check-sdui-manifest.mjs` :: exit 0
- `node scripts/check-sdui-manifest.mjs --self-test` :: exit 0
- `node scripts/pm/release-rehearsal-clone.mjs --self-test` :: exit 0
- `node scripts/release-pending-publish.mjs --self-test` :: exit 0
- `pnpm --dir ui exec vitest run scripts/` :: exit 0
- `pnpm --filter @objectstack/spec run check:duration-unit-keys` :: exit
0
- `pnpm check:changeset-gate-self-tests` :: exit 0
- `pnpm check:closed-ceremony-text` :: exit 0
- `pnpm check:cross-package-test-inputs` :: exit 0
- `pnpm check:dispatcher-error-vocabulary` :: exit 0
- `pnpm check:driver-memory-census` :: exit 0
- `pnpm check:engine-double-contract` :: exit 0
- `pnpm check:gitlink-declared` :: exit 0
- `pnpm check:nul-bytes` :: exit 0
- `pnpm check:published-files` :: exit 0
- `pnpm check:query-options-erasure` :: exit 0
- `pnpm check:refd-timer-probe` :: exit 0
- `pnpm check:registry-log-declared` :: exit 0
- `pnpm check:rest-log-spy-declared` :: exit 0
- `pnpm check:skill-identifier-liveness` :: exit 0
- `pnpm check:test-source-alias` :: exit 0
- `pnpm check:tier-file-adoption` :: exit 0
- `pnpm check:type-check-coverage` :: exit 0
- `pnpm check:type-check-debt` :: exit 0
- `pnpm check:undeclared-dep-imports` :: exit 0
- `pnpm check:watch-hint-literal` :: exit 0
- `pnpm check:where-matcher` :: exit 0
`pnpm --dir ui exec vitest run scripts/` first exited 1:
`markdown-test-inputs.test.ts` read the new test's `CHANGELOG.md` badge
fixture as a markdown input the ledger does not describe. The fixture
now spells the path `CHANGELOG`; re-run on the final head: `Test Files
170 passed | 2 skipped (172)`, exit 0. Because that commit landed while
the loop ran, the gates that read test files were re-run on the final
head, all exit 0: `check:test-source-alias`,
`check:cross-package-test-inputs`, `check:nul-bytes`,
`check:undeclared-dep-imports`, `check-comment-mask-corpus`,
`check-sdui-manifest`. `node scripts/pm/dispatch-gates.mjs --ran`: `35
derived famil(ies) accounted for — 35 run, 0 NOT-MEASURED`.
</details>
<details><summary>Standing ui gates</summary>
- `node scripts/check-sdui-manifest.mjs` (and `--self-test`): in the
derived list above, exit 0.
- `pnpm --dir ui check:new-line-citations`: `VERDICT
new-cross-file-line-citations: 0 new citation(s), enforcement
report-only -> exit 0`.
</details>
Dogfood battery: not owed (no `!` in the title, no `(narrowing)` arm).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CZbFePHuuidesS6BLC)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 644883e commit 677ac3b
5 files changed
Lines changed: 599 additions & 11 deletions
File tree
- .changeset
- ui/packages/app-shell/src/console/marketplace
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
10 | 18 | | |
11 | 19 | | |
12 | 20 | | |
| |||
16 | 24 | | |
17 | 25 | | |
18 | 26 | | |
19 | | - | |
20 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
21 | 32 | | |
22 | 33 | | |
23 | 34 | | |
| |||
32 | 43 | | |
33 | 44 | | |
34 | 45 | | |
| 46 | + | |
35 | 47 | | |
36 | 48 | | |
37 | 49 | | |
38 | | - | |
| 50 | + | |
39 | 51 | | |
40 | 52 | | |
41 | 53 | | |
42 | 54 | | |
43 | 55 | | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
44 | 103 | | |
45 | 104 | | |
46 | 105 | | |
| |||
148 | 207 | | |
149 | 208 | | |
150 | 209 | | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
151 | 233 | | |
152 | 234 | | |
153 | 235 | | |
| |||
195 | 277 | | |
196 | 278 | | |
197 | 279 | | |
198 | | - | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
199 | 284 | | |
200 | 285 | | |
201 | 286 | | |
| |||
218 | 303 | | |
219 | 304 | | |
220 | 305 | | |
221 | | - | |
222 | | - | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
223 | 371 | | |
224 | 372 | | |
225 | 373 | | |
| |||
247 | 395 | | |
248 | 396 | | |
249 | 397 | | |
250 | | - | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
251 | 405 | | |
252 | 406 | | |
253 | 407 | | |
| |||
265 | 419 | | |
266 | 420 | | |
267 | 421 | | |
268 | | - | |
| 422 | + | |
269 | 423 | | |
270 | 424 | | |
271 | 425 | | |
| |||
284 | 438 | | |
285 | 439 | | |
286 | 440 | | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
287 | 469 | | |
288 | 470 | | |
289 | 471 | | |
| |||
301 | 483 | | |
302 | 484 | | |
303 | 485 | | |
304 | | - | |
| 486 | + | |
305 | 487 | | |
306 | 488 | | |
307 | 489 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1402 | 1402 | | |
1403 | 1403 | | |
1404 | 1404 | | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
1405 | 1408 | | |
1406 | | - | |
| 1409 | + | |
1407 | 1410 | | |
1408 | 1411 | | |
1409 | 1412 | | |
| |||
1417 | 1420 | | |
1418 | 1421 | | |
1419 | 1422 | | |
1420 | | - | |
| 1423 | + | |
1421 | 1424 | | |
1422 | 1425 | | |
1423 | 1426 | | |
| |||
0 commit comments