Skip to content

fix(workspace): resolve a figure whose alt text holds balanced brackets - #869

Open
ANIRUDDHA ADAK (aniruddhaadak80) wants to merge 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/markdown-alt-bracket-depth
Open

ANIRUDDHA ADAK (aniruddhaadak80) wants to merge 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/markdown-alt-bracket-depth

Conversation

@aniruddhaadak80

Copy link
Copy Markdown
Contributor

What does this PR do, and why?

A manuscript figure whose alt text contains brackets never rendered in the preview.

The image scanner that rewrites relative image URLs to the file route ended the alt text at the first unescaped ], with no bracket-depth tracking:

const altEnd = close(value, cursor.value + 2, "]")

CommonMark allows balanced brackets inside link text, so ![Figure [a] b](figures/result.png) is an image with alt Figure [a] b. This scanner took the alt to be Figure [a, saw that the next character was a space rather than ( or [, failed to resolve a reference definition, and never recorded the image at all. markdownImages returned [], rewritePreviewImages left the relative path exactly as written, and the figure stayed broken. A nested image, ![outer ![inner](i.png)](y.png), was attributed to i.png instead of y.png.

The alt scan is now bracket-depth aware, reusing the same escaped() helper and depth idiom the destination parser in this file already uses. Unbalanced brackets are still not images, per the spec.

Linked issue

small fix, no issue

How did you verify it?

Before the fix:

![a [b] c](x.png)                 markdownImages -> []
                                 rewritePreviewImages -> "![a [b] c](x.png)"   (original relative URL kept)
![outer ![inner](i.png)](y.png)   -> {"alt":"outer ![inner","target":"i.png"}
![a [b] c][ref]                  -> []

After the fix:

![a [b] c](x.png)                 markdownImages -> [{"alt":"a [b] c","target":"x.png"}]
                                 rewritePreviewImages -> "![a [b\] c](/raw?path=reports%2Fx.png)"
![outer ![inner](i.png)](y.png)   -> {"alt":"outer ![inner](i.png)","target":"y.png"}
![a [b] c][ref]                  -> [{"alt":"a [b] c","target":"x.png"}]

Cross-checked against CommonMark 0.31.2 §6.4 (image descriptions follow the link-text rules, which "may contain balanced brackets, but not unbalanced ones, unless they are escaped"; example 520 ![[[foo](uri1)](uri2)](uri3) resolves to uri3).

Tests and gates:

  • cd frontend/workspace && bun test --timeout 15000 src/manuscript/model.test.ts — 9 pass, 0 fail (1 new test, 3 fixtures)
  • bun test --timeout 15000 src/manuscript/model.test.ts src/utils/markdown-assets.test.ts src/utils/markdown-file-links.test.ts src/atlas/FilePreviewMarkdown.test.ts — 29 pass, 0 fail
  • Regression proof: with only tooling/util/src/markdown.ts reverted and the new test left in place, 8 pass / 1 fail, and the single failure is the new case. The eight pre-existing cases — inline, local, remote and data images, titled and angle-bracketed destinations, balanced parens in the destination, reference-style image plus its definition, backtick span, fenced block, indented code block, CRLF offsets — all still pass, so the fix does not regress what already worked.
  • bun run --cwd tooling/util typecheck — exit 0
  • Prettier: checked against the committed blobs rather than this Windows working tree, which checks markdown out as CRLF and so reports a false failure. On the exact blob bytes CI will read: All matched files use Prettier code style!

Where the test lives, and why: the new case went into the existing frontend/workspace/src/manuscript/model.test.ts rather than a new test file under tooling/util. tooling/util/package.json has only a typecheck script, the root package.json has no test:util, and owner("tooling/util/src/markdown.ts") in tooling/repo/test-shards.ts is undefined — so a test placed there is executed by nothing in Fast CI or Deep CI. The workspace file already covers this parser through rewritePreviewImages and runs in Fast CI's workspace step on every PR.

Not run locally: bun run check in full, the aggregate bun run test:workspace, and Deep CI. bun run --cwd frontend/workspace build also fails in this Windows worktree, at frontend/ui/src/components/icon.tsx (esbuild: "The JSX import source cannot be set without also enabling React's automatic JSX transform"), which is a pre-existing consequence of the @synsci/* packages being resolved from outside the worktree and is unrelated to the two files this PR touches.

Checklist

  • bun run check is green (format, typecheck, backend + frontend/ui + SDK tests) — ran the workspace tests covering the change, the tooling/util typecheck and the format check; the full bun run check aggregate and the aggregate bun run test:workspace were not run locally
  • bun run --cwd frontend/workspace build succeeds if I touched frontend/workspace or frontend/ui — attempted; it fails in this worktree on frontend/ui/src/components/icon.tsx for an unrelated pre-existing reason (see above)
  • ./tooling/repo/generate.ts was run and the tooling/sdk output committed if I changed backend/cli/src/server — not applicable
  • CHANGELOG.md has an Unreleased entry if the change is user-visible
  • The matching docs page under frontend/docs/src/content/openscience/ is updated if behavior changed — no documented behaviour changes, markdown figures that already worked are unaffected
  • Screenshots or a short video are attached for UI changes — not applicable
  • No version bumps (package.json versions and tags are written by the release workflow)
  • install and frontend/landing/public/install are still byte-identical if I touched either — not applicable

The alt-text scan stopped at the first unescaped `]`, so
`![Figure [a] b](figures/result.png)` was never read as an image: the next
character was a space rather than `(` or `[`, no reference label matched, and
the manuscript preview kept the relative URL instead of the file route, leaving
the figure broken. An image description may hold balanced brackets, so the scan
now counts them with the same `escaped()` helper and `destination()` depth shape
it already uses for parentheses; the first `]` outside every unescaped pair
closes the alt text, and an unbalanced `]` still ends it. A nested image in the
alt text no longer shadows the image that contains it.

Covered by a case in frontend/workspace/src/manuscript/model.test.ts, which
fails against the previous scan.
@vercel

vercel Bot commented Sep 30, 2026

Copy link
Copy Markdown

ANIRUDDHA ADAK (@aniruddhaadak80) is attempting to deploy a commit to the InkVell Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

No deployments
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