mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 17:52:49 +00:00
fix(markdown): avoid empty web history measurements
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
Use `MarkdownRenderer` for chat replies, file previews, plans, pull requests and changelog content. It uses `react-native-enriched-markdown` on iOS, Android and web. Keep source adaptation and renderer configuration in `components/markdown`; consumers supply Markdown and, when needed, its file location.
|
||||
|
||||
Native selection belongs to the library's text view. Keep an assistant message in one row so selection can cross paragraphs, headings and lists. Code blocks and tables use the library's native views and their selection behavior. Web uses browser selection over semantic HTML. Chat still owns paced reveal, its render cap, message identity and list measurement; it does not choose a Markdown engine or classify prose.
|
||||
Native selection belongs to the library's text view. Keep an assistant message in one row so selection can cross paragraphs, headings and lists. Code blocks and tables use the library's native views and their selection behavior. Web uses browser selection over semantic HTML. Once its WASM parser is initialized, the library parses synchronously for the first commit of each mounted row; an empty interim row would corrupt virtualized measurements and move the reader. Chat still owns paced reveal, its render cap, message identity and list measurement; it does not choose a Markdown engine or classify prose.
|
||||
|
||||
## Links and images
|
||||
|
||||
Workspace file links use the shared file-link resolver. The workspace supplies its client, root and open-file action once; chat can supply the agent's more specific context. URLs use the same link press path. Recognized inline-code paths stay tappable. The library callback carries the tapped occurrence’s syntax context. Preserve it when opening files: explicit Markdown links decode URL escapes, while recognized inline-code paths preserve literal percent characters and use suffix lookup. Identical URL strings in different occurrences must stay distinct.
|
||||
Workspace file links use the shared file-link resolver. The workspace supplies its client, root and open-file action once; chat can supply the agent's more specific context. URLs use the same link press path. Recognized inline-code paths stay tappable. The library callback carries the tapped occurrence’s syntax context. Preserve it when opening files: explicit Markdown links decode URL escapes, while recognized inline-code paths preserve literal percent characters and use suffix lookup. Identical URL strings in different occurrences must stay distinct. Explicit absolute workspace links receive a relative hover title; existing authored titles remain intact.
|
||||
|
||||
Resolve workspace image paths over the existing session RPC, then give the library a cached native file URL or web blob URL. HTTP(S) images pass through unchanged. This preserves relay, SSH and local socket connections, which do not expose a daemon HTTP endpoint. Each mounted renderer owns its image acquisition identity, so a later reply reads a rewritten path again while the earlier mounted reply retains its snapshot. Image taps open the attachment lightbox. File previews resolve relative images against the Markdown file's directory.
|
||||
|
||||
|
||||
@@ -72,10 +72,10 @@ async function selectFileView(page: Page, view: "Preview" | "Source"): Promise<v
|
||||
}
|
||||
|
||||
async function expectMarkdownImageLoaded(page: Page, name: string): Promise<void> {
|
||||
const image = page.getByRole("img", { name, exact: true });
|
||||
const image = page.getByRole("button", { name, exact: true });
|
||||
await expect(image).toBeVisible();
|
||||
await expect
|
||||
.poll(() => image.locator("img").evaluate((element: HTMLImageElement) => element.naturalWidth))
|
||||
.poll(() => image.evaluate((element: HTMLImageElement) => element.naturalWidth))
|
||||
.toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ test.describe("CodeMirror workspace file editing", () => {
|
||||
const fileLink = page.getByRole("link", { name: "Open target" }).first();
|
||||
await expect(fileLink).toBeVisible({ timeout: 15_000 });
|
||||
await fileLink.hover();
|
||||
await expect(page.getByText("src/target.ts:42-45", { exact: true })).toBeVisible();
|
||||
await expect(fileLink).toHaveAttribute("title", "src/target.ts:42-45");
|
||||
} finally {
|
||||
await workspace.cleanup();
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ export async function expectImageSpaceReserved(page: Page, testInfo: TestInfo):
|
||||
await agent.client.waitForFinish(agent.agentId, 15_000);
|
||||
await openAgentTimeline(page, agent);
|
||||
await expectTimelinePromptVisible(page, "Show the delayed image");
|
||||
const image = page.getByRole("img", { name: "Reserved image" }).first();
|
||||
const image = page.getByRole("button", { name: "Reserved image", exact: true }).first();
|
||||
await expect(image).toBeVisible();
|
||||
const before = await image.boundingBox();
|
||||
await attachTimelineScreenshot(page, testInfo, "image-placeholder");
|
||||
@@ -508,8 +508,9 @@ export async function expectImageSpaceReserved(page: Page, testInfo: TestInfo):
|
||||
.toBe(240);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const box = await image.boundingBox();
|
||||
return box ? Math.round((box.width / box.height) * 100) : 0;
|
||||
return image.evaluate((element: HTMLImageElement) =>
|
||||
Math.round((element.naturalWidth / element.naturalHeight) * 100),
|
||||
);
|
||||
})
|
||||
.toBe(150);
|
||||
const after = await image.boundingBox();
|
||||
|
||||
@@ -34,7 +34,10 @@ export interface MarkdownImageContext {
|
||||
|
||||
export function useMarkdownSource(text: string, context: MarkdownImageContext) {
|
||||
const occurrenceKey = useId();
|
||||
const prepared = useMemo(() => prepareMarkdownSource(text), [text]);
|
||||
const prepared = useMemo(
|
||||
() => prepareMarkdownSource(text, context.workspaceRoot),
|
||||
[text, context.workspaceRoot],
|
||||
);
|
||||
const sources = [...new Set(prepared.images.map((image) => image.source))];
|
||||
const acquisitions = sources.map((source) => {
|
||||
const resolved = context.filePath
|
||||
|
||||
@@ -128,3 +128,9 @@ it("keeps diagram identity when an earlier workspace image resolves", async () =
|
||||
expect(before).toHaveProperty("id");
|
||||
expect(after?.id).toBe(before?.id);
|
||||
});
|
||||
|
||||
it("keeps workspace-relative hover titles on explicit absolute file links", () => {
|
||||
expect(prepareMarkdownSource("[Open](/repo/src/file.ts:42-45)", "/repo").render()).toBe(
|
||||
'[Open](/repo/src/file.ts:42-45 "src/file.ts:42-45")',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface MarkdownImage {
|
||||
}
|
||||
|
||||
/** Preserve source spelling; adapt workspace images, spoiler delimiters, and entity text. */
|
||||
export function prepareMarkdownSource(source: string) {
|
||||
export function prepareMarkdownSource(source: string, workspaceRoot?: string) {
|
||||
const images: MarkdownImage[] = [];
|
||||
let tree: Root | undefined;
|
||||
const getTree = () =>
|
||||
@@ -47,7 +47,12 @@ export function prepareMarkdownSource(source: string) {
|
||||
mdastExtensions: [gfmTableFromMarkdown()],
|
||||
}));
|
||||
const edits: { start: number; end: number; text: string }[] = [];
|
||||
if (!source.includes(")
|
||||
) {
|
||||
return { images, ...linkMetadata(), render: () => source };
|
||||
}
|
||||
|
||||
@@ -61,6 +66,15 @@ export function prepareMarkdownSource(source: string) {
|
||||
const start = node.position?.start.offset;
|
||||
const end = node.position?.end.offset;
|
||||
if (start !== undefined && end !== undefined) {
|
||||
if (
|
||||
node.type === "link" &&
|
||||
!node.title &&
|
||||
workspaceRoot &&
|
||||
node.url.startsWith(`${workspaceRoot}/`)
|
||||
) {
|
||||
const title = node.url.slice(workspaceRoot.length + 1);
|
||||
edits.push({ start: end - 1, end: end - 1, text: ` ${JSON.stringify(title)}` });
|
||||
}
|
||||
if (node.type === "tableRow") {
|
||||
// MD4C's always-enabled spoiler extension otherwise consumes adjacent cell
|
||||
// delimiters. Spaces keep empty GFM cells empty without producing spoilers.
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user