Skip to content

ENG-2227 Add link support to tldraw text shapes in Roam - #1441

Open
trangdoan982 wants to merge 3 commits into
mainfrom
eng-2227-add-link-support-to-tldraw-text-shapes-in-roam
Open

trangdoan982 wants to merge 3 commits into
mainfrom
eng-2227-add-link-support-to-tldraw-text-shapes-in-roam

Conversation

@trangdoan982

@trangdoan982 trangdoan982 commented Sep 14, 2026

Copy link
Copy Markdown
Member

Reviewer brief

  • Result: A text shape now exposes the same Edit link action as a geo shape — add, edit, open, remove — and the link persists across canvas reloads.
  • Review focus: The ticket says to reuse the geo-shape link code path. There is no such code in apps/roam to reuse: the whole feature is stock tldraw 2.4.6, gated on the duck-type check 'url' in shape.props (menu-hooks.ts:173), and the text shape has no url prop. So this is a schema change, not UI wiring — once the prop exists, the context-menu item and EditLinkDialog work unmodified because uiOverrides.tsx leaves ContextMenu, ActionsMenu and Dialogs at their defaults. Worth confirming that reading of the ticket.
  • Review focus: Migration v6 is load-bearing. Without it, pre-existing text shapes fail validation and the canvas will not open — migrateStoreSnapshot reports success and silently does nothing, then onValidationFailure rethrows even at phase === 'initialize'. It is version 6 of our own com.roam-research.discourse-graphs sequence rather than com.tldraw.shape.text/3 so a future tldraw release bumping its text sequence cannot collide.
  • Review focus: The sync worker validates text against stock props and would reject url, so the worker now extends the default text schema with the prop. The first attempt declared "text" to the worker as if it were a custom shape type; that blanks the entry to {}, which also drops textShapeMigrations, leaving the room at com.tldraw.shape.text version 0 against every client's 2 — Incompatible schema? on connect, and existing rooms failing to load. A delegated review caught it and syncWorkerRoomSchema.test.ts now pins both directions. The worker must be deployed with or before the extension, or text links in sync-mode canvases will fail validation server-side.
  • Risk or follow-up: Rolling back the extension cannot open a canvas this build has migrated — getMigrationsSince will not find com.roam-research.discourse-graphs/6. This is inherent to the existing migration pattern (the same is already true of v5), but worth a release note.
  • Risk or follow-up: Not yet exercised in Roam. See Verification.

Verification

  • pnpm ci:validate — passing, 5/5 tasks, 0 cached.
  • pnpm test:unit — 233 passed (7 new in src/utils/__tests__/textShapeLink.test.ts). Tests cover link persistence, migration idempotency, and the eligibility gate.
  • pnpm build — 0 errors; confirmed AddTextShapeUrl and tl-hyperlink-button are present in dist/extension.js.
  • Partially verified in the app. The Edit link action and a working link on a text shape have been seen in Roam. Screenshot feedback showed the icon sitting on top of the last word, because a text shape's bounds hug its glyphs while a geo shape has empty corner space; the icon now sits just outside the right edge. That reposition has not yet been confirmed visually in Roam, nor has persistence across a canvas close/reopen.

The migration deliberately only writes when url === undefined. Migration v3 originally assigned unconditionally and clobbered user values (#916); a test asserts double application is a no-op.

Loom video

Not recorded.

Scope check

  • Ran $scope-check against the ENG ticket and final diff.
  • Scope beyond Done When: The ticket asks to reuse the geo-shape code path; that path turned out to be stock tldraw rather than app code, so the implementation adds the url prop plus a migration instead. Same user-facing result, different mechanism than the ticket describes. The sync-worker change is not named in the ticket but is required for text links to survive in cloud-sync mode.

Local delegated full review

  • Ran a comprehensive review of the entire final diff in a subagent with a fresh context.

It found a release-blocking bug in the sync worker (above), a broken class-name concatenation on the link button, and that the original tests asserted against a hand-rolled schema rather than the real utils. All three are fixed in 9c42b8423; tests now derive their schema from baseShapeUtils so they fail if the util swap or the props ever regress.

🤖 Generated with Claude Code

Text shapes could not carry a link, so a canvas narrative could not point at
its detailed source. tldraw gates its built-in Edit link action on
`'url' in shape.props`, and the stock text shape has no such prop, so the fix
is schema-level rather than UI wiring.

Subclass TextShapeUtil to add a `url` prop and render the hyperlink button,
and replace the stock util through a shared list so all four stores agree.
Existing text shapes are backfilled by migration v6 of the repo's own
sequence; without it they fail validation and the canvas will not open.

The sync worker validates `text` against stock props, so the client now
declares it alongside the custom shape types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 14, 2026

Copy link
Copy Markdown

ENG-2227

@supabase

supabase Bot commented Sep 14, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Sep 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
discourse-graph Skipped Skipped Sep 18, 2026 9:08pm UTC

Request Review

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread apps/roam/src/components/canvas/TextShapeWithLinkUtil.tsx Outdated
Declaring "text" to the sync worker as if it were a custom shape type blanked
its schema entry to {}, which dropped textShapeMigrations. The room then
reported com.tldraw.shape.text version 0 against every client's 2, so
getMigrationsSince returned 'Incompatible schema?' and every client was
rejected on connect while existing rooms failed to load. The worker now
extends the default text schema instead, keeping its migrations, and refuses
to blank any default shape type. Covered by syncWorkerRoomSchema.test.ts.

A text shape's bounds hug its glyphs, so tldraw's in-bounds link button landed
on the last word. Sit it just outside the right edge instead.

Also fix a missing separator that merged the two hyperlink button class names
into one invalid token, guard isTextShapeRecord against a null props object,
and assert that the text util was actually replaced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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