Skip to content

feat: add deterministic Markdown endpoint for tabs#3278

Open
linouxis9 wants to merge 5 commits into
jo-inc:masterfrom
linouxis9:feat/markdown-endpoint
Open

feat: add deterministic Markdown endpoint for tabs#3278
linouxis9 wants to merge 5 commits into
jo-inc:masterfrom
linouxis9:feat/markdown-endpoint

Conversation

@linouxis9
Copy link
Copy Markdown

@linouxis9 linouxis9 commented May 16, 2026

Summary

Adds GET /tabs/:tabId/markdown for deterministic Markdown rendering of the current tab, backed by Playwright accessibility snapshots.

  • Adds view=document (default): readable Markdown without refs/control noise.
  • Adds view=agent: agent-friendly Markdown preserving actionable refs, controls, states, and values.
  • Keeps the existing GET /tabs/:tabId/snapshot behavior intact for backward compatibility.
  • Adds Markdown-specific offset pagination and per-view cache entries.
  • Uses Playwright AI aria snapshots where available for ref-capable snapshots.
  • Raises playwright-core to ^1.59.1, for page.ariaSnapshot({ mode: 'ai' }) support.
  • Updates OpenAPI, README docs, and test scripts so unit/non-e2e and e2e suites run through the correct Jest configs.

Why

Raw accessibility snapshots are useful for automation but noisy for reading/extraction. This endpoint gives callers a stable Markdown surface without forcing consumers to post-process snapshot YAML themselves, while view=agent preserves actionability for future agent workflows.

API

GET /tabs/:tabId/markdown?userId=<id>&view=document&offset=0
GET /tabs/:tabId/markdown?userId=<id>&view=agent&offset=0

Response shape mirrors snapshot pagination fields:

{
  "url": "https://example.com",
  "view": "document",
  "markdown": "# Example\n\n...",
  "refsCount": 12,
  "truncated": false,
  "totalChars": 1234,
  "offset": 0,
  "hasMore": false,
  "nextOffset": null
}

view=document: clean, no refs

# Example Domain

This domain is for use in documentation examples without needing permission.

[Learn more](https://iana.org/domains/example)
# Markdown - Wikipedia

[Jump to content](#bodyContent)

## Header
- [Wikipedia The Free Encyclopedia](/wiki/Main_Page), [Donate](https://donate.wikimedia.org/...), [Create account](...), [Log in](...)

## Contents
- [(Top)](#)
- [History](#History)
- [Rise and divergence](#Rise_and_divergence)
- [CommonMark](#CommonMark)
- [Variants](#Variants)
  - [GitHub Flavored Markdown](#GitHub_Flavored_Markdown)
  - [Markdown Extra](#Markdown_Extra)
...

# Markdown
From Wikipedia, the free encyclopedia

Markdown is a lightweight markup language for creating formatted text using a plain-text editor.
John Gruber created Markdown in 2004 as an easy-to-read markup language.

| Markdown |  |
| --- | --- |
| Filename extensions | `.md`, `.markdown` |
| Internet media type | `text/markdown` |
| Developed by | John Gruber |
...

view=agent — refs and controls preserved

# Example Domain

This domain is for use in documentation examples without needing permission.

[Learn more](https://iana.org/domains/example)[e1]
# Markdown - Wikipedia

[Jump to content](#bodyContent)[e1]

## Header
- <button "Main menu" [e2] cursor=pointer>, [Wikipedia The Free Encyclopedia](/wiki/Main_Page)[e3],
  <searchbox "Search Wikipedia" [e4]>, <button "Search" [e5]>, [Donate](...)[e6], ...

## Contents
<button "hide" [e9] cursor=pointer>
- [(Top)](#)[e10]
- [History](#History)[e11]
- [Variants](#Variants)[e14] <button "Toggle Variants subsection" [e15] expanded>
  - [GitHub Flavored Markdown](#GitHub_Flavored_Markdown)[e16]
  - [Markdown Extra](#Markdown_Extra)[e17]
...

# Markdown
<button "Go to an article in another language. Available in 33 languages" [e24] cursor=pointer>

## Navigation
- [Article](/wiki/Markdown)[e25], [Talk](/wiki/Talk:Markdown)[e26]
- [Read](/wiki/Markdown)[e27], [Edit](/w/index.php?title=Markdown&action=edit)[e28],
  [View history](/w/index.php?title=Markdown&action=history)[e29]
...
## Header
- <button "Main menu" [e2] cursor=pointer>, [Wikipedia The Free Encyclopedia](/wiki/Main_Page)[e3],
  <searchbox "Search Wikipedia" [e4]>, <button "Search" [e5]>

/snapshot: unchanged (raw aria YAML)

- heading "Example Domain" [level=1]
- paragraph: This domain is for use in documentation examples...
- paragraph:
  - link "Learn more" [e1] :
    - /url: https://iana.org/domains/example

Implementation

Renderer (lib/markdown.js, ~1,400 lines):

  1. Parses Playwright AI aria snapshot YAML into a role tree.
  2. Annotates list nesting, table headers, and code languages.
  3. Renders per role — inline vs block, ref/control emission controlled by view.
  4. Postprocesses: normalizes whitespace, deduplicates adjacent lines, strips skip-to-content cruft.
  5. Windows for offset pagination with Markdown-specific truncation markers.

Design choices:

  • view is an enum, not a toggle. agent mode renders inline controls with states/values, keeps interactive chrome visible, and marks un-annotated elements no ref.
  • Refs are never invented. Reuses the existing snapshot ref-annotation pipeline (buildRefsannotateAriaSnapshotWithRefs).
  • Separate caches. tabState.lastMarkdown is a per-view Map; snapshot and markdown pagination never cross-contaminate.
  • Compact chrome. Regions like nav, header, and footer fold into ## Sidebar: [link1], [link2] lines.

Route (server.js):

  • Refactored shared ref-annotation helpers (parseSnapshotRoleLine, annotateAriaSnapshotWithRefs, buildAnnotatedAriaSnapshot) used by both /snapshot and /markdown.
  • getAriaSnapshot accepts { mode }; mode='ai' tries page.ariaSnapshot({ mode: 'ai' }) with body-locator fallback.
  • clearRenderedContentCaches(tabState) replaces scattered lastSnapshot = null assignments across navigate, click, type, scroll, press, back, forward, refresh, and viewport.
  • Google SERP handling: reuses extractGoogleSerp directly.

Testing

node --check server.js
node --check lib/markdown.js
NODE_OPTIONS='--experimental-vm-modules' npx jest tests/unit/markdown.test.js tests/unit/openapi.test.js --runInBand --forceExit
NODE_OPTIONS='--experimental-vm-modules' npx jest --config jest.config.e2e.cjs tests/e2e/markdown.test.js tests/e2e/playwrightAiSnapshotCapabilities.test.js --runInBand --forceExit
npm test
npm run test:e2e

Unit tests (tests/unit/markdown.test.js: 22 tests): empty input, common shapes (headings, paragraphs, links, images, lists, tables, code), document ref/control stripping, agent ref/control preservation, YAML-quoted keys, legacy @e refs, safe code fences, inferred table headers, layout table flattening, GFM task lists, private-use glyph filtering, duplicate line collapsing.

E2E tests (tests/e2e/markdown.test.js: 7 tests): document default, agent view, invalid view → 400, /snapshot backcompat, offset pagination with Markdown markers, per-view cache isolation, cache invalidation on navigation.

AI snapshot capability (tests/e2e/playwrightAiSnapshotCapabilities.test.js): verifies page.ariaSnapshot({ mode: 'ai' }) is available and produces [ref=eN] annotations.

Backwards compatibility

  • /tabs/:tabId/snapshot: unchanged. Response shape, query params, and pagination behavior preserved.
  • Internal ref-annotation helper extraction is behavior-preserving.
  • No existing routes modified in breaking ways.

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