<?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: Qiang Li</title>
    <description>The latest articles on DEV Community by Qiang Li (@fenglq02).</description>
    <link>https://dev.to/fenglq02</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4098332%2Fbee9bed3-2581-4286-931b-1abd14ed22b9.png</url>
      <title>DEV Community: Qiang Li</title>
      <link>https://dev.to/fenglq02</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fenglq02"/>
    <language>en</language>
    <item>
      <title>Designing a Cross-Device Workout Workflow Around YouTube Timestamps</title>
      <dc:creator>Qiang Li</dc:creator>
      <pubDate>Fri, 28 Aug 2026 04:48:19 +0000</pubDate>
      <link>https://dev.to/fenglq02/designing-a-cross-device-workout-workflow-around-youtube-timestamps-51f</link>
      <guid>https://dev.to/fenglq02/designing-a-cross-device-workout-workflow-around-youtube-timestamps-51f</guid>
      <description>&lt;p&gt;Saving a YouTube workout and &lt;em&gt;using&lt;/em&gt; it in a repeatable training system are different product problems.&lt;/p&gt;

&lt;p&gt;A bookmark preserves a URL. A training workflow needs to preserve intent: which movement matters, where it starts, why it belongs in a routine, and what the user should do next.&lt;/p&gt;

&lt;p&gt;While building &lt;a href="https://www.trainflow.me" rel="noopener noreferrer"&gt;TrainFlow&lt;/a&gt;, I have been exploring an architecture that treats timestamped video moments as reusable domain objects and separates desktop planning from mobile execution. This post explains the design decisions behind that workflow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: I am the maker of TrainFlow. The examples below describe the product and engineering patterns I used, not a generic recommendation disguised as a review.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Model the useful moment, not the whole video
&lt;/h2&gt;

&lt;p&gt;A YouTube URL is a source reference, not an exercise definition. One video may contain a warm-up, three demonstrations, a progression, and a cooldown. The reusable unit is usually a moment inside the video.&lt;/p&gt;

&lt;p&gt;A conceptual model can stay small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExerciseAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;startSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;endSeconds&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cue&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warmup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strength&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conditioning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recovery&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timestamp is stored as a number rather than as part of a formatted string. That makes validation, sorting, duration calculations, and player integration easier.&lt;/p&gt;

&lt;p&gt;The original URL remains important. It preserves attribution and lets the user return to the full context. The action is a pointer into the source, not a copied replacement for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Normalize YouTube URLs at the boundary
&lt;/h2&gt;

&lt;p&gt;YouTube links arrive in several shapes: standard watch URLs, short links, mobile shares, and links that already contain a &lt;code&gt;t&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;Do not let every component interpret these independently. Normalize once when the user creates an action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NormalizedVideoRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;startSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A boundary function should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse the URL with the platform URL API.&lt;/li&gt;
&lt;li&gt;Accept only supported YouTube hosts.&lt;/li&gt;
&lt;li&gt;Extract and validate the video ID.&lt;/li&gt;
&lt;li&gt;Convert timestamp formats such as &lt;code&gt;90&lt;/code&gt;, &lt;code&gt;1m30s&lt;/code&gt;, or existing query parameters into seconds.&lt;/li&gt;
&lt;li&gt;Reject negative or non-finite values.&lt;/li&gt;
&lt;li&gt;Store canonical data rather than the original query-string shape.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps player code boring. It receives a validated video ID and a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Separate authoring from execution
&lt;/h2&gt;

&lt;p&gt;TrainFlow uses a Next.js App Router application with two distinct surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a desktop dashboard for building actions and routines&lt;/li&gt;
&lt;li&gt;a mobile PWA for running a session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is more than responsive CSS. The two contexts have different jobs.&lt;/p&gt;

&lt;p&gt;Desktop authoring benefits from density. Users compare sources, edit notes, reorder actions, and see the whole routine. Mobile execution benefits from focus. It should show the current action, the relevant video moment, a small number of controls, and a clear next step.&lt;/p&gt;

&lt;p&gt;Trying to force both jobs into one universal screen usually leaves the desktop view too sparse and the mobile view too busy.&lt;/p&gt;

&lt;p&gt;The shared domain model still matters. Both surfaces should read the same action and routine data, but they can render different interaction models around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Make routines ordered compositions
&lt;/h2&gt;

&lt;p&gt;Once actions are reusable, a routine becomes an ordered composition rather than a copied block of video metadata.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RoutineItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;actionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;noteOverride&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Routine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RoutineItem&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Referencing an action avoids duplicating the source URL and timestamp every time it appears. A routine item can carry a small override when the session context differs, while the core action remains reusable.&lt;/p&gt;

&lt;p&gt;Ordering should be explicit. Relying on creation time or database return order will eventually produce surprising sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Treat workout execution as a state machine
&lt;/h2&gt;

&lt;p&gt;A session is easier to reason about when it has explicit states. Even a small state machine is better than a collection of unrelated booleans.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SessionStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&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;p&gt;Events then define valid transitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;START&lt;/code&gt;: ready → playing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COMPLETE_ACTION&lt;/code&gt;: playing → resting or the next action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RESUME&lt;/code&gt;: resting or paused → playing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FINISH&lt;/code&gt;: playing → completed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps with edge cases. What happens if the app is backgrounded during rest? Can a completed session be resumed? Should the next clip preload? These questions become transition rules instead of scattered UI conditions.&lt;/p&gt;

&lt;p&gt;Persist the minimum state needed to recover a session: the routine version, current item, status, and relevant timing data. A mobile PWA should assume interruptions are normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keep AI output editable
&lt;/h2&gt;

&lt;p&gt;AI can reduce the cost of turning a long video into structured actions. It can propose titles, timestamps, cues, or a first routine draft.&lt;/p&gt;

&lt;p&gt;The important design choice is to keep those fields editable and visibly provisional.&lt;/p&gt;

&lt;p&gt;Transcripts may be incomplete. A video can demonstrate several variations. Exercise names are not always standardized. An automated system also cannot decide whether a movement is appropriate for a specific person.&lt;/p&gt;

&lt;p&gt;A reliable workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI produces a draft.&lt;/li&gt;
&lt;li&gt;The user reviews the source moment.&lt;/li&gt;
&lt;li&gt;The user edits the action.&lt;/li&gt;
&lt;li&gt;Only the reviewed version enters a routine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Store provenance when it is useful, but do not make the interface feel like an audit log. The product goal is faster review, not blind automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Log only what supports the next decision
&lt;/h2&gt;

&lt;p&gt;It is tempting to build a large workout-event schema immediately. Start smaller.&lt;/p&gt;

&lt;p&gt;A useful first session record may contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;routine ID and version&lt;/li&gt;
&lt;li&gt;started and completed timestamps&lt;/li&gt;
&lt;li&gt;completed action count&lt;/li&gt;
&lt;li&gt;a short user note&lt;/li&gt;
&lt;li&gt;optional perceived difficulty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is enough to answer practical questions: Did the user finish? How long did it take? What should change next time?&lt;/p&gt;

&lt;p&gt;Additional metrics should earn their place by improving a future decision. Otherwise they add friction to the execution flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Design for link durability
&lt;/h2&gt;

&lt;p&gt;External video content can change. A video may become unavailable, timestamps may no longer match an edited source, or a creator may restrict playback.&lt;/p&gt;

&lt;p&gt;The application should fail gracefully:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep the action title and user notes available&lt;/li&gt;
&lt;li&gt;show a clear source-unavailable state&lt;/li&gt;
&lt;li&gt;allow the URL or timestamp to be repaired&lt;/li&gt;
&lt;li&gt;never silently replace the source&lt;/li&gt;
&lt;li&gt;avoid claiming ownership of third-party content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Durability comes from preserving the user’s structure even when an external dependency fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  The larger pattern
&lt;/h2&gt;

&lt;p&gt;This architecture applies beyond workouts. Any workflow built from long-form media can benefit from the same layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;normalize the source&lt;/li&gt;
&lt;li&gt;capture a precise moment&lt;/li&gt;
&lt;li&gt;turn it into a reusable object&lt;/li&gt;
&lt;li&gt;compose objects into an ordered plan&lt;/li&gt;
&lt;li&gt;execute with a focused state machine&lt;/li&gt;
&lt;li&gt;record the minimum useful result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key is to stop treating saved content as completed work. A bookmark is only the beginning of a workflow.&lt;/p&gt;

&lt;p&gt;For TrainFlow, that means turning YouTube timestamps into actions, actions into routines, and routines into sessions that are practical to run on a phone. The technical stack matters, but the product boundary matters more: authoring and execution are related tasks, not the same interface.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>pwa</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
