<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aidan Nulman</title>
    <description>The latest articles on DEV Community by Aidan Nulman (@anulman).</description>
    <link>https://dev.to/anulman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F283621%2F9cad1584-6fd5-4189-b5ec-584ac927bb08.jpeg</url>
      <title>DEV Community: Aidan Nulman</title>
      <link>https://dev.to/anulman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anulman"/>
    <language>en</language>
    <item>
      <title>Recording Links: The Nitty Gritty Details Behind Today's Launch</title>
      <dc:creator>Aidan Nulman</dc:creator>
      <pubDate>Wed, 04 Jun 2025 19:19:37 +0000</pubDate>
      <link>https://dev.to/jamdotdev/recording-links-the-nitty-gritty-details-behind-todays-launch-3bdp</link>
      <guid>https://dev.to/jamdotdev/recording-links-the-nitty-gritty-details-behind-todays-launch-3bdp</guid>
      <description>&lt;p&gt;Earlier today we announced Recording Links, the easiest way for Product teams to capture screen recordings, repro steps, and user feedback directly inside their app, without requiring a Chrome extension or install.&lt;/p&gt;

&lt;p&gt;To do so, we had to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design a simple + attractive recorder UX you’d happily send to your users&lt;/li&gt;
&lt;li&gt;Make sure it works &lt;em&gt;reliably&lt;/em&gt; and &lt;em&gt;in every major browser vendor&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Keep the install process as simple as it can be, and no simpler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As one of the authors on this project I’m &lt;em&gt;clearly&lt;/em&gt; biased, but I think the solution we’ve shipped is just shy of magic. And although magicians never reveal their secrets, I write software and have been asked to cook for a minute about our work. So I’ll just tell you: we did it all with &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k4gkfimjp1aw3h862v7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k4gkfimjp1aw3h862v7.png" alt="The capture frame our scripts inject on your website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But before we go too deep: try Recording Links yourself, or check out this demo Ian recorded!&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/GP0SBqWU-58"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prior Art
&lt;/h2&gt;

&lt;p&gt;Recording Links builds on Jam’s core extension and more-recent Jam for Customer Support (Intercom) products. Both of these are built on Jam’s core object model and capture stack, which allow us to deliver a consistent debugging experience in our own app and the others we integrate with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3woqzyy3mc1df0q3wg4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3woqzyy3mc1df0q3wg4c.png" alt="Example from a Jam's "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While each of our products have somewhat different feature sets, Jam’s customers expect a few core promises from our products: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users can easily start, stop, restart, and submit recordings&lt;/li&gt;
&lt;li&gt;Events must be captured between start of recording and end of recording&lt;/li&gt;
&lt;li&gt;We can’t lose data along the way, lest our users (or worse, yours!) lose trust in us&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fulfilling these promises in the extension can sometimes be a challenge. But, since users’ data is local, it’s at least reasonably straightforward to fulfill our promises.&lt;/p&gt;

&lt;p&gt;This was comparatively difficult in our Intercom product. Specifically, that product sends users to a Recorder hosted at &lt;code&gt;recorder.jam.dev&lt;/code&gt;, which—due to browser security constraints—uses a websocket to communicate with event capture scripts installed on our users’ sites.&lt;/p&gt;

&lt;p&gt;The websocket approach works fine enough, but has room for improvement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If our socket server is unavailable—e.g. briefly during deploys, or a user is recording while offline—created Jams may be corrupted or missing data; there is no way for the recorder script to recognize this state and inform the user.&lt;/li&gt;
&lt;li&gt;Common Internet chaos—e.g. slow connections, out-of-order packets, unexpected user and/or script interactions—requires mitigation, thereby increasing code complexity and ongoing maintenance&lt;/li&gt;
&lt;li&gt;It literally doesn’t work in Safari—and may someday not work in other browsers—because it relies on third-party cookies to establish a shared identifier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of our design goals for Recording Links was to remove this network dependency altogether. The only time we expect to hear from your users is when they’re explicitly submitting a Jam.&lt;/p&gt;

&lt;h2&gt;
  
  
  State Partitioning
&lt;/h2&gt;

&lt;p&gt;This lofty design goal introduces a classic problem: “&lt;em&gt;how do we communicate between tabs locally?”&lt;/em&gt;. Most browser primitives (e.g. localStorage, BroadcastChannels…) are restricted to same-origin communication. Only Safari is so strict (Chrome and Firefox restrict to same-domain), but since same-origin is our lowest-common denominator let’s stick with it.&lt;/p&gt;

&lt;p&gt;Our earlier architecture—a Jam-hosted &lt;code&gt;recorder.jam.dev&lt;/code&gt; URL—can’t communicate directly with scripts on pages you host, because it’s a different origin. But what if we put an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; on your pages from the same-origin as our &lt;code&gt;recorder&lt;/code&gt;? Well, that’s not so simple. Here are &lt;a class="mentioned-user" href="https://dev.to/kentcdodds"&gt;@kentcdodds&lt;/a&gt; and &lt;a class="mentioned-user" href="https://dev.to/ryanflorence"&gt;@ryanflorence&lt;/a&gt; discussing the issue in Oct 2023:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8fpu0zp7g5cq9ds5m4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8fpu0zp7g5cq9ds5m4d.png" alt="@kentcdodds and @ryanflorence discussing iframe peculiarities [[x.com](https://x.com/ryanflorence/status/1709602360845324544)]"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All browsers use a technique called “State Partitioning” to prevent cross-site tracking. What this means practically is that recorder.jam.dev frames embedded on example.com belong to a “state partition” keyed by the top-frame’s origin (i.e. example.com:recorder.jam.dev), whereas our top frame is in the top-level partition (i.e. recorder.jam.dev).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsh6sabltsr1d00qjj8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsh6sabltsr1d00qjj8y.png" alt="The top-level  raw `recorder.jam.dev` endraw  cannot communicate with the embedded one"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We started by exploring how to bring your origin to our contents, e.g. by asking installers to set up a recorder.example.com subdomain and point it to our content. But since Safari is strictly same-origin, we cannot assume that recorder.example.com can natively communicate with example.com. In fact, doing so would fail our expectation of supporting all major browser vendors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For9ful0fq1v87xtjpr9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For9ful0fq1v87xtjpr9a.png" alt=" raw `recorder.example.com` endraw  is same-domain but _not_ same-origin to  raw `example.com` endraw "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our next idea was to bring our contents to your origin, by embedding both the Capture and the Recorder scripts onto your pages via iframe. This way, both capture.js and recorder.js scripts would live in the example.com:recorder.jam.dev partition, and could communicate to each other!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihr5vw0gvkm8aj283vl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihr5vw0gvkm8aj283vl8.png" alt="Both scripts embedded from  raw `recorder.jam.dev` endraw  are in the same state partition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rest of the work was not easy—try building an app that works equally well in embedded vs. non-embedded states, or reliably streaming video content across an iframe boundary in all major desktop browsers—but it was mostly app work, on top of infrastructure we could trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed Embeds
&lt;/h2&gt;

&lt;p&gt;Having determined our architecture requirements, we also prioritized figuring out exactly how we would distribute our iframes. We had previously considered a few other mechanisms—the CNAME approach of course, and a “proxy recorder” where embedders’ servers fetched and served our content directly—but there were only two meaningful mechanisms in our iframe bucket:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DIRECT EMBED—&lt;/strong&gt;where embedders directly embed an iframe with a URL we provide:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Recorder Page&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- YOUR PAGE'S `&amp;lt;head&amp;gt;` --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://recorder.jam.dev/recorder/PASS-THE-ID"&lt;/span&gt;
      &lt;span class="na"&gt;sandbox=&lt;/span&gt;&lt;span class="s"&gt;"allow-scripts allow-same-origin allow-popups"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MANAGED EMBED—&lt;/strong&gt;where embedders use a script we provide to mount our iframe:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- on all capture-able pages --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://js.jam.dev/capture.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- at least on recorder pages --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://js.jam.dev/recorder.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also wanted to make sure that Jams recorded on your pages only get sent to you—you don’t want someone from &lt;a href="http://evil.com" rel="noopener noreferrer"&gt;&lt;code&gt;evil.com&lt;/code&gt;&lt;/a&gt; to spoof an &lt;a href="http://example.com" rel="noopener noreferrer"&gt;&lt;code&gt;example.com&lt;/code&gt;&lt;/a&gt; Recording Link and steal the privileged network and console logs from an unsuspecting user.&lt;/p&gt;

&lt;p&gt;To achieve this, we needed to design a domain verification strategy. We considered three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over DNS—&lt;/strong&gt;configure a TEXT record with a value we expect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwp6o2xen6ga11iaxtvsj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwp6o2xen6ga11iaxtvsj.png" alt="Example DNS setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over HTTP—&lt;/strong&gt;return a response we expect from a configured endpoint
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/PROVIDED-URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PROVIDED-BODY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In HTML—&lt;/strong&gt;attest the teams you allow wherever you load the recorder script
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://js.jam.dev/recorder.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"jam:team"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"TEAM-ID-COPIED-FROM-SETTINGS"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"jam:team"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"ANOTHER-TEAM-ID-FROM-SETTINGS"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- more head content --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- body content --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider the above options with regards to three criteria we’d set out while spec’ing the work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Prefer copy/paste-able solutions vs. ones that require config&lt;/li&gt;
&lt;li&gt;Prefer solutions implementable by lower-authority personnel&lt;/li&gt;
&lt;li&gt;Prefer solutions that look familiar and obvious vs. strange and opaque&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Managed Embeds w/ In-HTTP Domain Verification is just… cleaner. There’s only one line that requires config, and we don’t have to ask users to update their site code should we need different iframe attributes, for example. Additionally, neither of these require the implementer have permission to edit DNS settings. And everyone recognizes script embeds and meta tags!&lt;/p&gt;

&lt;p&gt;As with above, the rest of the work was not easy—coordinating state across multiple frame boundaries is challenging!—but we thought it much more convenient for users and maintainable for us to own this complexity. I’m still impressed we managed to pack such a powerful product into three lines of HTML!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We have ambitious plans for Recording Links: new ways to create them, new ways to consume them, new ways to make them even easier to use. Today, we celebrate how far we’ve come; Recording Links has surpassed many of our expectations internally, and we hope it delivers you not just the value but the &lt;em&gt;wows&lt;/em&gt; we’ve been experiencing.&lt;/p&gt;

&lt;p&gt;Have you tried Recording Links yet? If not, what are you waiting for? It’s &lt;a href="https://jam.dev/docs/product-features/recording-links" rel="noopener noreferrer"&gt;only 3 lines of HTML to get started&lt;/a&gt;; I’d order you a coffee while you work, but let’s be real—if you made it this far, by the time the coffee was ready you’d already be done.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
