<?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: Nickwang · Build Notes</title>
    <description>The latest articles on DEV Community by Nickwang · Build Notes (@nickwangstudio).</description>
    <link>https://dev.to/nickwangstudio</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%2F4240720%2Fd80897ea-1f76-41b0-ad5c-d4eb608cc7ef.png</url>
      <title>DEV Community: Nickwang · Build Notes</title>
      <link>https://dev.to/nickwangstudio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nickwangstudio"/>
    <language>en</language>
    <item>
      <title>A balanced rota can still fail: a two-person scheduling example</title>
      <dc:creator>Nickwang · Build Notes</dc:creator>
      <pubDate>Sat, 14 Nov 2026 04:57:20 +0000</pubDate>
      <link>https://dev.to/nickwangstudio/a-balanced-rota-can-still-fail-a-two-person-scheduling-example-3213</link>
      <guid>https://dev.to/nickwangstudio/a-balanced-rota-can-still-fail-a-two-person-scheduling-example-3213</guid>
      <description>&lt;p&gt;A rota can give everyone the same number of assignments and still leave a shift uncovered. The problem often starts with a reasonable shortcut: assign the next shift to the person with the lowest current workload.&lt;/p&gt;

&lt;p&gt;Here is a small, fictional example that exposes the limitation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Person&lt;/th&gt;
&lt;th&gt;Morning&lt;/th&gt;
&lt;th&gt;Afternoon&lt;/th&gt;
&lt;th&gt;Maximum assignments&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ada&lt;/td&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ben&lt;/td&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;td&gt;Unavailable&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both shifts need one person. At the start, Ada and Ben have equal workloads. If a greedy tie-break assigns Ada to the morning, the afternoon is left without an eligible person: Ben is unavailable, and Ada has reached her limit.&lt;/p&gt;

&lt;p&gt;Yet a complete schedule exists: &lt;strong&gt;Ben in the morning, Ada in the afternoon&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate constraints from preferences
&lt;/h2&gt;

&lt;p&gt;Availability and maximum assignments are hard constraints. A schedule must satisfy them before its workload balance is useful.&lt;/p&gt;

&lt;p&gt;For a slightly larger example, the checks might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every shift has its required number of people.&lt;/li&gt;
&lt;li&gt;Every assigned person is available for that shift.&lt;/li&gt;
&lt;li&gt;Nobody exceeds their own assignment limit.&lt;/li&gt;
&lt;li&gt;One person cannot cover two assignments in the same time group.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After those checks, compare feasible schedules using an explicit objective. One simple choice is to minimize the difference between the largest and smallest assignment counts, then use the sum of squared counts as a tie-break.&lt;/p&gt;

&lt;p&gt;That is a definition of fairness for this model. It does not account for shift difficulty or individual preferences unless those are represented in the inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A complete search you can inspect
&lt;/h2&gt;

&lt;p&gt;For the two-person example, checking all combinations is small enough to write directly. This standalone JavaScript runs in Node.js and uses no packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"morning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"afternoon"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Ben"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"morning"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;feasible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;afternoon&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;assignments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;afternoon&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"morning"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="n"&gt;afternoon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"afternoon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;withinLimits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;assignments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;withinLimits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;feasible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;afternoon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;afternoon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feasible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// [{ morning: "Ben", afternoon: "Ada" }]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example checks availability and assignment limits. It has four combinations, so completing the search is easy. Larger instances need better ordering and pruning, and they still need an explicit stopping rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  A search limit changes what the result means
&lt;/h2&gt;

&lt;p&gt;A useful scheduling interface should distinguish these outcomes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;What the result establishes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Optimal&lt;/td&gt;
&lt;td&gt;A feasible schedule meets a proven lower bound or a complete search established the best objective.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feasible, search limit reached&lt;/td&gt;
&lt;td&gt;The shown schedule satisfies the modeled constraints; a better schedule might exist.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infeasible&lt;/td&gt;
&lt;td&gt;A constraint check or a complete search established that no schedule satisfies the modeled constraints.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown, search limit reached&lt;/td&gt;
&lt;td&gt;The search stopped without finding a feasible schedule. This does not establish infeasibility.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The final two states are easy to confuse in a UI. A time limit is a statement about how much searching happened, not a proof about every possible schedule.&lt;/p&gt;

&lt;p&gt;The same distinction is useful in packing, timetabling and small resource-allocation tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the inputs with the result
&lt;/h2&gt;

&lt;p&gt;An exported schedule is easier to review when it travels with its assumptions. Keep the availability table, individual limits, time groups and solver status alongside the assignments. If an input changes, recompute the result before sharing it.&lt;/p&gt;

&lt;p&gt;For an initial check, use a tiny example where you already know the answer. Then try an impossible case, such as two required assignments with a total capacity of one. Finally, test an invalid input and make sure an old successful result is not left looking current.&lt;/p&gt;

&lt;p&gt;I used these checks while preparing &lt;strong&gt;Fair Schedule Planner&lt;/strong&gt;, a small offline browser tool with editable availability, personal limits, shared time groups, CSV results and a workbook for recording assumptions. The supplied examples are fictional. The interface reports the search outcomes above.&lt;/p&gt;

&lt;p&gt;If you want the ready-made interface and worksheets, the &lt;a href="https://payhip.com/b/KR72D?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=fair_schedule_counterexample" rel="noopener noreferrer"&gt;Fair Schedule Planner download is available on Payhip&lt;/a&gt; for &lt;strong&gt;$12 as a one-time purchase&lt;/strong&gt;. A free PDF preview is available on the product page. The JavaScript example in this article is independent and free to use.&lt;/p&gt;

&lt;p&gt;This article and the product were prepared with AI assistance. The example output was checked, and the product's documented functional checks cover availability, conflicting assignments, impossible inputs and exported result data.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Before asking AI to build an app, write five answers and one acceptance check</title>
      <dc:creator>Nickwang · Build Notes</dc:creator>
      <pubDate>Sat, 14 Nov 2026 01:19:50 +0000</pubDate>
      <link>https://dev.to/nickwangstudio/before-asking-ai-to-build-an-app-write-five-answers-and-one-acceptance-check-138l</link>
      <guid>https://dev.to/nickwangstudio/before-asking-ai-to-build-an-app-write-five-answers-and-one-acceptance-check-138l</guid>
      <description>&lt;p&gt;A five-line brief gives an AI-assisted prototype a concrete starting point: one user, one trigger and an output you can inspect.&lt;/p&gt;

&lt;p&gt;Consider a fictional product idea called LessonWrap, intended to help independent tutors write a weekly lesson report. Start by deciding what one completed workflow would look like.&lt;/p&gt;

&lt;p&gt;Replace it with five concrete answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User: an independent tutor who already has lesson notes.&lt;/li&gt;
&lt;li&gt;Trigger: writing the weekly report after the final Friday lesson.&lt;/li&gt;
&lt;li&gt;Current workaround: copy notes into a document and manually check unfinished follow-ups.&lt;/li&gt;
&lt;li&gt;Observable output: an editable report and a visible list of items still to do.&lt;/li&gt;
&lt;li&gt;First-version boundary: no real student accounts, billing or automatic messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This brief creates a useful next question: what would count as the prototype working?&lt;/p&gt;

&lt;p&gt;Use synthetic records to define the first checks. Empty input should produce a clear message. A malformed record should identify the field that needs correcting. A valid fictional lesson should produce a report that can still be edited. A download should preserve the words the user entered, including symbols and non-English text. The tool should explain what happens to edits when the page closes.&lt;/p&gt;

&lt;p&gt;Write each check as an input and a result you can inspect. This gives you a way to judge the next implementation pass.&lt;/p&gt;

&lt;p&gt;Try this prompt with your own brief:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Convert this brief into six acceptance checks. For each check, provide a synthetic input, an observable expected result, and the boundary of what the check proves. Keep unknowns as unknowns. Do not invent user validation, performance figures or integrations.&lt;/p&gt;

&lt;p&gt;Brief: [paste your five answers here]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next pass should look for missing decisions. Does the prototype send data to a server? Can a user inspect the generated output before it leaves the page? Are the examples fictional? Does export create a backup that import can actually restore? These questions matter more than adding another screen to the demo.&lt;/p&gt;

&lt;p&gt;A compact acceptance record could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"case"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"backup_round_trip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"a fictional lesson report with an edited follow-up"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"export a backup, clear the form, import that backup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"the edited follow-up returns unchanged"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"proves"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"the backup preserves the tested fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"does_not_prove"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"real classroom adoption or production security"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have a small working slice, record what you checked and what remains untested. Then use feedback to change the brief. You do not need to manufacture a market survey to justify the next prototype.&lt;/p&gt;

&lt;p&gt;I prepared AI MVP Blueprint as a downloadable workbench for this process. It includes six editable worksheets, twelve prompts, English and Chinese guides, CSV files and the clearly fictional LessonWrap example. The desktop browser tool works offline and creates text and JSON exports. A visible download link lets you save the file after inspecting its content. Session edits need an explicit backup before closing.&lt;/p&gt;

&lt;p&gt;The planning exercise above is free to use. The ready-made workbook is a paid product at $9: &lt;a href="https://nickster2735.gumroad.com/l/ai-mvp-blueprint" rel="noopener noreferrer"&gt;https://nickster2735.gumroad.com/l/ai-mvp-blueprint&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a planning toolkit, with no AI account, API credits, hosting or custom development included. The text and toolkit were prepared with AI assistance, and the examples are fictional.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Rendering vertical video one frame at a time: three runnable JavaScript examples</title>
      <dc:creator>Nickwang · Build Notes</dc:creator>
      <pubDate>Thu, 12 Nov 2026 10:01:54 +0000</pubDate>
      <link>https://dev.to/nickwangstudio/i-open-sourced-25-code-rendered-vertical-video-methods-stop-losing-the-prompt-lottery-4a4h</link>
      <guid>https://dev.to/nickwangstudio/i-open-sourced-25-code-rendered-vertical-video-methods-stop-losing-the-prompt-lottery-4a4h</guid>
      <description>&lt;p&gt;The public AI Short Video Method Library now includes three runnable MIT examples: GSAP cards and typography, Vega/D3 charts, and Mermaid diagrams. Each scene can be moved to a requested frame, captured in a browser, and encoded as a vertical video.&lt;/p&gt;

&lt;p&gt;This article was prepared with an AI assistant from the repository code and the recorded server runs. Nickwang Studio maintains the repository and sells a separate complete edition with 25 method projects, guides and demonstration videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the public examples
&lt;/h2&gt;

&lt;p&gt;Install the Node.js version specified in the README and make sure FFmpeg is available on your command line. Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Nickwang684/ai-short-video-method-library.git
&lt;span class="nb"&gt;cd&lt;/span&gt; ai-short-video-method-library
npm run setup
npm run render:gsap
npm run render:data
npm run render:diagram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup command installs the JavaScript dependencies and Chromium. On Linux, a browser launch error may identify missing system libraries that need to be installed for that environment.&lt;/p&gt;

&lt;p&gt;The output files are &lt;code&gt;output/m01.mp4&lt;/code&gt;, &lt;code&gt;output/m10.mp4&lt;/code&gt; and &lt;code&gt;output/m16.mp4&lt;/code&gt;. The command &lt;code&gt;npm run render:all&lt;/code&gt; runs all three.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Example directory&lt;/th&gt;
&lt;th&gt;What to inspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;examples/m01-gsap-html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A paused GSAP timeline, staggered cards and SVG animation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;examples/m10-data-viz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vega/D3 chart state at a requested frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;examples/m16-diagram-as-code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mermaid SVG with staged nodes and connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;examples/_shared&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local file serving, screenshot capture and video encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Separate scene time from capture time
&lt;/h2&gt;

&lt;p&gt;The shared capture script expects a scene to expose &lt;code&gt;SCENE_META&lt;/code&gt; and &lt;code&gt;seekFrame(frame)&lt;/code&gt;. A simplified scene interface looks like this; the scene must create its own timeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SCENE_META&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;270&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seekFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&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;At 30 fps, frame 90 represents three seconds of scene time. Capturing that frame may take longer than 1/30 of a second. The requested scene state determines the image; the encoding input rate determines its playback timing.&lt;/p&gt;

&lt;p&gt;The actual capture loop waits for the scene's seek function and two browser drawing callbacks before taking each screenshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;durationInFrames&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seekFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shotOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&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 &lt;code&gt;write&lt;/code&gt; helper handles pipe backpressure so screenshots do not keep accumulating while FFmpeg is still consuming earlier input. A scene can return a Promise from &lt;code&gt;seekFrame&lt;/code&gt; when it needs to complete an asynchronous update.&lt;/p&gt;

&lt;p&gt;The drawing callbacks do not establish that fonts, images, network data or WebGL resources have loaded. Those dependencies need their own readiness checks. Repeatability also depends on input data, assets, fonts and renderer versions. A live browser walkthrough needs separate capture checks because the external page can change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the recorded runs produced
&lt;/h2&gt;

&lt;p&gt;On November 12, 2026, the project server ran the documented commands against commit &lt;code&gt;8be3897061983b5f55e80b88b597528def9d4aa5&lt;/code&gt;. FFprobe reported:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;th&gt;Frame rate&lt;/th&gt;
&lt;th&gt;Frames&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m01.mp4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1080×1920&lt;/td&gt;
&lt;td&gt;30 fps&lt;/td&gt;
&lt;td&gt;270&lt;/td&gt;
&lt;td&gt;9 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m10.mp4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1080×1920&lt;/td&gt;
&lt;td&gt;30 fps&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;10 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m16.mp4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1080×1920&lt;/td&gt;
&lt;td&gt;30 fps&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;10 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The capture script uses FFmpeg's &lt;code&gt;image2pipe&lt;/code&gt; input and &lt;code&gt;libx264&lt;/code&gt;, with &lt;code&gt;scale=in_range=full:out_range=tv,format=yuv420p&lt;/code&gt; in its output filter. You can inspect your own output with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe -v error -select_streams v:0 -count_frames \
  -show_entries &lt;span class="nv"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;width,height,r_frame_rate,nb_read_frames:format=duration \
  -of json output/m01.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the frame count differs, check the scene metadata and capture log. For a size mismatch, check the viewport. For a different animation state, inspect the scene inputs, random seeds, fonts and dependency versions. Pixel comparisons across different machines would be a separate experiment; the recorded checks above establish dimensions, frame count and duration for these three server runs.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/Nickwang684/ai-short-video-method-library" rel="noopener noreferrer"&gt;AI Short Video Method Library on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five complete copies for developer trials
&lt;/h2&gt;

&lt;p&gt;There are five free complete-edition copies for developers who want to try one method and report the actual result. Pick a technique that fits a real task, and check its README for required tools or services. The complete edition contains the 25 method projects, guides and rendered demonstrations; the public repository contains the three runnable examples described above and a method catalog.&lt;/p&gt;

&lt;p&gt;To join, comment with your operating system, intended use and the method you want to try. Then &lt;a href="https://nickster2735.gumroad.com/l/ai-video-methods-complete/DEVSEED5NOV26" rel="noopener noreferrer"&gt;claim a complete copy with the 100% discount&lt;/a&gt;. These five copies are shared across all recruitment posts. The code is limited to new customers of the store.&lt;/p&gt;

&lt;p&gt;After trying it, reply here or open a repository issue with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The method, OS, tool versions and exact command you ran.&lt;/li&gt;
&lt;li&gt;Whether it produced the expected output, including dimensions and duration when available.&lt;/li&gt;
&lt;li&gt;The first unclear or failed step, with the relevant error and README section.&lt;/li&gt;
&lt;li&gt;One useful part and the first change you would make.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An unsuccessful run is useful feedback. A positive review is not required. Reports will be retained in their original form and linked to any resulting fixes.&lt;/p&gt;

&lt;p&gt;Update note, November 13, 2026: this revision corrects the earlier claim that all 25 methods were open-sourced and replaces unsupported reproducibility claims with the recorded checks above.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
