<?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: hiro@</title>
    <description>The latest articles on DEV Community by hiro@ (@hirodeath).</description>
    <link>https://dev.to/hirodeath</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%2F57464%2F7deedba2-079f-4a50-91e6-065b4e20a0c0.jpeg</url>
      <title>DEV Community: hiro@</title>
      <link>https://dev.to/hirodeath</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hirodeath"/>
    <language>en</language>
    <item>
      <title>What belongs in a Plane Page, and what belongs in a Work Item?</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Sat, 26 Sep 2026 02:11:18 +0000</pubDate>
      <link>https://dev.to/hirodeath/what-belongs-in-a-plane-page-and-what-belongs-in-a-work-item-3d2p</link>
      <guid>https://dev.to/hirodeath/what-belongs-in-a-plane-page-and-what-belongs-in-a-work-item-3d2p</guid>
      <description>&lt;p&gt;The more thoroughly you describe a feature, the longer its Work Item becomes. Background, future plans, acceptance criteria, and test results accumulate in one description. Eventually, the implementer has to search for the scope of the current change.&lt;/p&gt;

&lt;p&gt;A useful boundary is how long the information remains relevant. Put assumptions shared by multiple changes in a Page, and keep the conditions for completing the current change in its Work Item.&lt;/p&gt;

&lt;p&gt;The example is SquadNote, a personal project. On September 11, 2026, its Plane Project was inspected through read-only access and compared with documents in Git. The Project's Pages listing contained 0 entries at that time. The Page structure below is a proposal based on existing tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the different jobs inside a long parent ticket
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SQN-29&lt;/code&gt; is the parent ticket for introducing a staged transition from trial participation to full membership. Its description covers trial permissions, manual promotion and termination, communication, an application flow, progress tracking, and follow-up after expiration.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SQN-30&lt;/code&gt;, titled “[Step 1] Implement trial participant permissions and manual promotion and termination” in translation, is a concrete implementation task. Its scope includes API authorization, compatibility with existing invitation URLs, and promotion and termination behavior. Communication features and automatic expiration processing are outside that scope.&lt;/p&gt;

&lt;p&gt;Both descriptions contain requirements, but those requirements serve different purposes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Information&lt;/th&gt;
&lt;th&gt;When it is needed&lt;/th&gt;
&lt;th&gt;Proposed location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ending a trial removes the relationship with an organization, not the entire account&lt;/td&gt;
&lt;td&gt;Across later implementation stages&lt;/td&gt;
&lt;td&gt;Shared rules in a Page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The initial stage uses manual promotion and termination&lt;/td&gt;
&lt;td&gt;To define this change&lt;/td&gt;
&lt;td&gt;Work Item scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permissions agree across Web, iOS, and Android&lt;/td&gt;
&lt;td&gt;To determine whether this change is complete&lt;/td&gt;
&lt;td&gt;Work Item acceptance criteria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A later stage will handle follow-up after expiration&lt;/td&gt;
&lt;td&gt;To plan subsequent work&lt;/td&gt;
&lt;td&gt;Parent and later Work Items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What was checked, against which revision&lt;/td&gt;
&lt;td&gt;During review or retesting&lt;/td&gt;
&lt;td&gt;A reference to a test document in Git&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The material worth extracting into a Page is the context likely to be copied into several implementation tasks. Moving the current task's scope there too would make the ticket insufficient for judging completion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep shared rules and their rationale in the Page
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.plane.so/core-concepts/pages/overview" rel="noopener noreferrer"&gt;Plane's Pages documentation&lt;/a&gt; describes Pages as a place for project requirements and other documents. Work Item mentions and creating Work Items from selected text carried a Business label when checked. The proposed information structure does not depend on having those editing conveniences.&lt;/p&gt;

&lt;p&gt;For this feature, an initial Page could be as small as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trial participation

Purpose
- Represent participation before a person decides to become a full member

Shared rules
- Ending a trial ends the relationship with the organization
- It does not delete the entire account
- Enforce permissions in the API as well as the interface

Scope by stage
- Initial: manual promotion and termination
- Later: communication, applications, progress, and follow-up after expiration

Related work
- Parent ticket
- Work Items for each stage

Change history
- Assumptions that changed and the reasons for changing them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a proposed outline extracted from the tickets, not an excerpt from an existing Page. It does not introduce another implementation-status checklist.&lt;/p&gt;

&lt;p&gt;When a shared rule changes, review the affected open Work Items as well as editing the Page. For example, changing which history must survive trial termination also changes the termination API's acceptance criteria. Links help locate the affected work; they do not keep the linked descriptions consistent automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leave testable completion conditions in the Work Item
&lt;/h2&gt;

&lt;p&gt;A reference to the shared rule “enforce permissions in the API” is not enough to define this implementation. The ticket needs conditions that someone can check. The following examples make the existing scope more explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A trial participant cannot retrieve data by calling an API outside their permissions.&lt;/li&gt;
&lt;li&gt;Promoting a participant gives them the required permissions in that organization.&lt;/li&gt;
&lt;li&gt;Ending the trial does not delete the entire account or relationships with other organizations.&lt;/li&gt;
&lt;li&gt;The existing invitation flow still works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These conditions let a reviewer separately assess alignment with the Page and completion of the current ticket.&lt;/p&gt;

&lt;p&gt;Adding future communication features to the same checklist would prevent the manual-promotion task from closing even after its implementation is finished. Link subsequent work without adding it to the current definition of done.&lt;/p&gt;

&lt;h2&gt;
  
  
  A test record describes a different state from a specification
&lt;/h2&gt;

&lt;p&gt;The related repository contains &lt;code&gt;docs/testing/sqn-30-trial-participant-checklist.md&lt;/code&gt;. It was inspected at &lt;code&gt;circle-hub&lt;/code&gt; commit &lt;code&gt;0cda1e8&lt;/code&gt;; the document's addition appears in commit &lt;code&gt;12cbb78&lt;/code&gt;. The review used this &lt;a href="https://github.com/takahiro-saeki/circle-hub/tree/0cda1e865ad80d1529197729d8d436e96d56edc7" rel="noopener noreferrer"&gt;fixed repository revision&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The document has completed Web checks and unfinished manual mobile checks. It also limits the work to verification in the development environment, excluding production deployment.&lt;/p&gt;

&lt;p&gt;That supports the statement that a test document exists and distinguishes checked from unchecked items. A feature plan in the parent ticket, or a partially completed checklist, does not establish completion on every device. The application checks were not rerun for this article either.&lt;/p&gt;

&lt;p&gt;The Page describes the intended rules, the Work Item describes the change, and the test document records findings for a particular revision. Linking the Work Item to the test document's commit can preserve that time reference. Development invitation details do not belong in a public specification example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the split with one long ticket
&lt;/h2&gt;

&lt;p&gt;Start by trying the split with one parent ticket. Choose one with repeated shared assumptions, extract those into a Page, and check that its child Work Items still contain their scope and acceptance criteria.&lt;/p&gt;

&lt;p&gt;Then ask whether someone can start work from a child ticket and whether a changed shared rule can be traced to the affected tasks. No Pages were created during this investigation, so there is no measured reduction in search time to report.&lt;/p&gt;

&lt;p&gt;A small fix may fit comfortably in one description. The reason to create a Page is that its assumptions need to remain useful across later work, rather than that the current ticket has reached a particular length.&lt;/p&gt;

</description>
      <category>plane</category>
      <category>productivity</category>
      <category>architecture</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Zero updated rows do not fail a D1 batch: testing the rollback boundary</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Fri, 25 Sep 2026 02:05:50 +0000</pubDate>
      <link>https://dev.to/hirodeath/zero-updated-rows-do-not-fail-a-d1-batch-testing-the-rollback-boundary-3odn</link>
      <guid>https://dev.to/hirodeath/zero-updated-rows-do-not-fail-a-d1-batch-testing-the-rollback-boundary-3odn</guid>
      <description>&lt;p&gt;Suppose a D1 &lt;code&gt;batch()&lt;/code&gt; reduces stock and then saves an order. If the UPDATE affects zero rows when stock is insufficient, will the order also remain unsaved?&lt;/p&gt;

&lt;p&gt;In a local D1 binding experiment, the INSERT succeeded even when the UPDATE affected zero rows. Throwing a JavaScript exception after &lt;code&gt;batch()&lt;/code&gt; returned did not remove the saved order.&lt;/p&gt;

&lt;p&gt;SQL failure and an unfulfilled business operation need separate treatment. &lt;code&gt;batch()&lt;/code&gt; provides atomicity when SQL fails. It does not automatically turn the application's conclusion that stock was unavailable into a SQL error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a SQL statement that fails
&lt;/h2&gt;

&lt;p&gt;The official &lt;a href="https://developers.cloudflare.com/d1/worker-api/d1-database/#batch" rel="noopener noreferrer"&gt;D1Database documentation&lt;/a&gt; describes sequential execution of the statement array, aborting or rolling back the sequence if a statement fails. The result array corresponds to the input statement order.&lt;/p&gt;

&lt;p&gt;This small schema makes it possible to examine what qualifies as failure. It is an experiment about the guarantee, not a change deployed to a real inventory service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before each case, the tables are reset and &lt;code&gt;stock&lt;/code&gt; receives a row with &lt;code&gt;id = 1&lt;/code&gt; and &lt;code&gt;remaining = 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, an UPDATE reduces the stock by one, followed by an INSERT using an order ID that was already registered before the batch:&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;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE stock SET remaining = remaining - 1 WHERE id = 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO orders VALUES (?, ?)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;duplicate&lt;/span&gt;&lt;span class="dl"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call throws a primary-key uniqueness error. Reading the database after catching it shows that the remaining stock is back to one and only the previously registered order exists. The earlier UPDATE was rolled back too.&lt;/p&gt;

&lt;p&gt;In a separate successful batch containing UPDATE, INSERT, and SELECT, the final SELECT returned the updated remaining stock of zero. This also confirmed that later statements can observe earlier changes in the sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  No matching row is not a SQL error
&lt;/h2&gt;

&lt;p&gt;Next, try to order two units when only one remains:&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="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE stock SET remaining = remaining - ? WHERE id = 1 AND remaining &amp;gt;= ?&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="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO orders (id, quantity) VALUES (?, ?)&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="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order-b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output was &lt;code&gt;[0, 1]&lt;/code&gt;. The UPDATE changes nothing because the remaining stock fails its predicate. The statement itself does not fail, however. The INSERT succeeds, leaving stock at one and saving an order for two units.&lt;/p&gt;

&lt;p&gt;There is no JavaScript branch inside this batch that examines the UPDATE result and cancels the INSERT. Both statements were placed in an array and submitted in advance.&lt;/p&gt;

&lt;p&gt;A conditional UPDATE can help determine whether an individual update occurred. When another table's update must depend on that outcome, you also need to design how zero affected rows influence the later operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Throwing after the batch is too late
&lt;/h2&gt;

&lt;p&gt;After receiving the result, the experiment ran this check:&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt; &lt;span class="o"&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No stock was reserved&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 exception occurs. Reading the database after catching it still finds &lt;code&gt;order-b&lt;/code&gt;. By the time &lt;code&gt;await db.batch(...)&lt;/code&gt; returns successfully, its SQL execution has completed. A later JavaScript exception does not undo that batch.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;SQL result&lt;/th&gt;
&lt;th&gt;Final remaining stock&lt;/th&gt;
&lt;th&gt;Orders&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Successful update and registration of one unit&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;One new order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate ID in later INSERT&lt;/td&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Only the preexisting order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two-unit request excluded by WHERE&lt;/td&gt;
&lt;td&gt;Success, changes are zero and one&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;One new two-unit order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negative stock rejected by CHECK&lt;/td&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Zero orders&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The final row comes from the following variation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can a constraint express the invalid outcome?
&lt;/h2&gt;

&lt;p&gt;The fixture includes a CHECK constraint that disallows negative stock. Removing the stock predicate from the UPDATE makes a two-unit decrement violate that constraint:&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;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE stock SET remaining = remaining - ? WHERE id = 1&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="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO orders (id, quantity) VALUES (?, ?)&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="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order-c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;This case fails with a CHECK error, leaving one unit and zero orders. For this input, expressing the invalid business outcome as a SQL constraint violation makes the entire batch fail.&lt;/p&gt;

&lt;p&gt;The snippet is not a complete inventory system. If the stock row with &lt;code&gt;id = 1&lt;/code&gt; does not exist, the UPDATE still affects zero rows. Validating quantities, relating products to orders, repeated order submissions, and multiple products are also outside this experiment. One CHECK constraint does not detect every unsuccessful business operation.&lt;/p&gt;

&lt;p&gt;When choosing an implementation, list the conditions that should count as failure. For each one, determine whether SQL throws or completes successfully with zero changes. For the latter, examine whether conditions or constraints within SQL can connect it to the later update, or whether the unit of work needs to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduction environment and limits
&lt;/h2&gt;

&lt;p&gt;The experiment ran on September 11, 2026, with Node.js &lt;code&gt;v24.15.0&lt;/code&gt;, Wrangler &lt;code&gt;4.81.1&lt;/code&gt;, and Miniflare &lt;code&gt;4.20260409.0&lt;/code&gt;. Wrangler's &lt;a href="https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy" rel="noopener noreferrer"&gt;getPlatformProxy&lt;/a&gt; supplied the local D1 binding with &lt;code&gt;persist: false&lt;/code&gt; and &lt;code&gt;remoteBindings: false&lt;/code&gt;. The configuration requested compatibility date &lt;code&gt;2026-09-11&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/experiments/article-stock-2026-09/d1-batch.mjs" rel="noopener noreferrer"&gt;experiment script&lt;/a&gt; includes table creation, all four cases, and assertions on the resulting rows. Its first argument is the path to an installed Wrangler package's &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node experiments/article-stock-2026-09/d1-batch.mjs &lt;span class="se"&gt;\&lt;/span&gt;
  ../circle-hub-multi-device-push/apps/web/node_modules/wrangler/package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local binding is an emulation of production. This is not a load or failure test against production D1. It also does not test lost network responses, external payment or notification systems, or multiple concurrent requests.&lt;/p&gt;

&lt;p&gt;A later &lt;code&gt;throw&lt;/code&gt; did not undo the batch that had already returned successfully. Grouping statements is only part of the work: identify which SQL statement expresses each invalid outcome as an error.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>sql</category>
      <category>database</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Split Plane Workspaces or Projects based on who may administer the data</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Thu, 24 Sep 2026 01:49:09 +0000</pubDate>
      <link>https://dev.to/hirodeath/split-plane-workspaces-or-projects-based-on-who-may-administer-the-data-36gb</link>
      <guid>https://dev.to/hirodeath/split-plane-workspaces-or-projects-based-on-who-may-administer-the-data-36gb</guid>
      <description>&lt;p&gt;Putting personal projects and company work in one Plane workspace can be appealing. It avoids switching screens and keeps today's tasks together.&lt;/p&gt;

&lt;p&gt;The mistake is assuming that a Private Project also creates a space independent of workspace administrators. Before splitting projects, decide whether the administrators of the same Workspace may see the information.&lt;/p&gt;

&lt;p&gt;This is a design proposal based on official documentation checked on September 11, 2026. It is not an investigation of a company Workspace or a record of migrating an existing Plane setup. Actual permissions require checking the edition, role configuration, and installed version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Private Projects still have an administrator exception
&lt;/h2&gt;

&lt;p&gt;The official &lt;a href="https://docs.plane.so/core-concepts/projects/overview" rel="noopener noreferrer"&gt;Project documentation&lt;/a&gt; limits Private Projects to explicitly invited members while also allowing Workspace Admins to access them.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.plane.so/roles-and-permissions/member-roles" rel="noopener noreferrer"&gt;Member roles reference&lt;/a&gt; adds that the former workspace-level Admin role was renamed Owner. Owners have access throughout the Workspace, and Admins can access project content without being added as project members.&lt;/p&gt;

&lt;p&gt;The official pages contain both older and newer role names. Rather than deciding from the word “Admin” in a screen, check whether the role administers the Workspace or only an individual Project.&lt;/p&gt;

&lt;p&gt;Personal tasks that should not be shared with company administrators do not belong in a Private Project inside the company Workspace. If the administrator is shared and only the usual participants or workflow need to differ, Projects remain a possible boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write down whether the boundary concerns administration or work
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.plane.so/core-concepts/workspaces/overview" rel="noopener noreferrer"&gt;Workspace overview&lt;/a&gt; describes a Workspace as the unit containing Projects, with membership and integration settings managed at that level.&lt;/p&gt;

&lt;p&gt;The following are example design choices, not a list of settings already applied to an environment:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reason for separation&lt;/th&gt;
&lt;th&gt;Unit to consider&lt;/th&gt;
&lt;th&gt;Deciding factor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Personal and company work have different administrators&lt;/td&gt;
&lt;td&gt;Workspace&lt;/td&gt;
&lt;td&gt;Keep membership and administrative authority separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One person administers both a web app and a game&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;Shared administrator, different work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A project in one organization needs limited participation&lt;/td&gt;
&lt;td&gt;Private Project&lt;/td&gt;
&lt;td&gt;Workspace administrator access is acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An improvement effort is open to the organization&lt;/td&gt;
&lt;td&gt;Public Project&lt;/td&gt;
&lt;td&gt;Workspace members can discover and join it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Creating another Workspace merely because the ticket count grew mixes organizational convenience with an administrative boundary. Decide who administers the data first, then split Projects within that scope.&lt;/p&gt;

&lt;p&gt;Personal work with multiple collaborators does not automatically belong in a single Workspace either. Name the people who will keep administering it, decide invitations, and receive access to the work. Then determine whether they fit under the same administration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public Projects and Internet publication are separate
&lt;/h2&gt;

&lt;p&gt;“Public” can also cause confusion. Public in a Project's visibility settings is described as allowing Workspace members to discover and join the Project themselves. Guests need an explicit invitation.&lt;/p&gt;

&lt;p&gt;There is a documentation distinction here: the Project overview describes member access broadly, while the roles page says that access to content requires joining the Project. These descriptions do not establish identical behavior for every screen. For design purposes, treat it as a scope that ordinary members can join themselves, and verify it with the actual role.&lt;/p&gt;

&lt;p&gt;A separate &lt;a href="https://docs.plane.so/core-concepts/deploy" rel="noopener noreferrer"&gt;Publish Project feature&lt;/a&gt; creates a public page that can be viewed without signing up.&lt;/p&gt;

&lt;p&gt;These are therefore separate settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which people in the Workspace can join or access the Project.&lt;/li&gt;
&lt;li&gt;Whether viewers outside the Workspace receive a public page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A request for an external roadmap does not mean every detail in the working Project needs to become public. Include a procedure for choosing what to expose. No publishing or permission changes were performed for this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your administrator view is not a complete permission test
&lt;/h2&gt;

&lt;p&gt;Being able to open a Project as its administrator does not establish what an ordinary member sees.&lt;/p&gt;

&lt;p&gt;For verification in the target environment, use test data without confidential content and distinguish an ordinary member who has not joined the Project from a Workspace administrator. For both Private and Public Projects, record whether each role can discover it, open its URL directly, and join without an invitation.&lt;/p&gt;

&lt;p&gt;Check published pages in a browser that is not logged into the Workspace. Testing participation permissions inside a Project and testing a published page's visibility are separate tasks. Those checks with different roles were not performed in this investigation.&lt;/p&gt;

&lt;p&gt;Separate Workspaces also do not, by themselves, establish separate account authentication or isolation of a self-hosted infrastructure. The boundary discussed here is Plane's administrative unit. Infrastructure isolation and company authentication policy require their own checks.&lt;/p&gt;

&lt;p&gt;When unsure where to split, first answer: may the person administering this Workspace open this Project? If yes, choose the Project's visibility and participants within it. If no, reconsider which Workspace should contain it. That order keeps organizational convenience from obscuring the administrative boundary.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>projectmanagement</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
    <item>
      <title>When retrying scheduled publication avoids duplicates, and when a lost response breaks it</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Wed, 23 Sep 2026 01:59:56 +0000</pubDate>
      <link>https://dev.to/hirodeath/when-retrying-scheduled-publication-avoids-duplicates-and-when-a-lost-response-breaks-it-7g8</link>
      <guid>https://dev.to/hirodeath/when-retrying-scheduled-publication-avoids-duplicates-and-when-a-lost-response-breaks-it-7g8</guid>
      <description>&lt;p&gt;Whether it is safe to rerun a failed publishing command depends on where it failed. Retrying before an external service creates the article can attempt a new publication. Retrying after creation, when only the response was lost, can create another article.&lt;/p&gt;

&lt;p&gt;This repository stores Qiita's &lt;code&gt;id&lt;/code&gt; and dev.to's &lt;code&gt;devto_id&lt;/code&gt; in the article files, then uses those IDs for updates on subsequent runs. That supports recovery when the IDs have been saved. It does not provide complete duplicate prevention.&lt;/p&gt;

&lt;p&gt;The publisher at commit &lt;code&gt;8a1bfa8&lt;/code&gt; was executed with external APIs and file operations mocked to establish that boundary. This article describes the implementation and a proposed recovery procedure for its remaining gap. It does not report an actual duplicate-publication incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  A saved ID turns the next attempt into an update
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;scripts/publish-scheduled.mjs&lt;/code&gt; reads the selected day's entry and handles Japanese publication before English. For Qiita, an existing &lt;code&gt;id&lt;/code&gt; selects PATCH; otherwise it uses POST. For dev.to, &lt;code&gt;devto_id&lt;/code&gt; selects PUT; otherwise it uses POST.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST to Qiita
  ↓ obtain id from the response
Save id in the Japanese file
  ↓
POST to dev.to
  ↓ obtain id from the response
Save devto_id in the English file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When both files have the required IDs and published flags, the script exits successfully without calling an API.&lt;/p&gt;

&lt;p&gt;When only the Japanese side is complete, the behavior is slightly different. The current implementation runs the Qiita operation again, but uses PATCH because it has the ID. It then POSTs to dev.to, which still has no ID. Describing this as "retrying only the English post" would not match the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduled attempts and retries inside one job serve different purposes
&lt;/h2&gt;

&lt;p&gt;The workflow schedules a start corresponding to 08:55 JST and another at 09:10. The earlier start waits until 09:00. Within a job, it tries the publisher up to 3 times, waiting 30 seconds and then 60 seconds after failures.&lt;/p&gt;

&lt;p&gt;A retry within that job can reuse IDs already saved in its files. A separate run needs the changed frontmatter to have been committed to Git. The workflow has a metadata persistence step using &lt;code&gt;if: always()&lt;/code&gt;, but that does not guarantee success if the step or its push fails.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;concurrency&lt;/code&gt; group also limits simultaneous publishing by workflows in that group. It does not make the external service's POST idempotent.&lt;/p&gt;

&lt;p&gt;GitHub &lt;a href="https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule" rel="noopener noreferrer"&gt;documents delays under high load&lt;/a&gt;. Adding another scheduled attempt does not guarantee publication at the desired time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the actual script without sending externally
&lt;/h2&gt;

&lt;p&gt;The experiment ran on September 11, 2026, with Node.js &lt;code&gt;v24.15.0&lt;/code&gt;. The &lt;a href="https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/experiments/article-stock-2026-09/publishing-recovery.mjs" rel="noopener noreferrer"&gt;reproduction script&lt;/a&gt; reads the publisher from Git, removes its two imports, and runs it in a VM. It supplies test replacements for file access, &lt;code&gt;fetch&lt;/code&gt;, and environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node experiments/article-stock-2026-09/publishing-recovery.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No real API requests are sent. The fake server increments its article count whenever it accepts a POST and can lose the response afterward. Fake files persist between attempts, exposing the difference between failing before and after an ID is saved.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;First attempt&lt;/th&gt;
&lt;th&gt;Calls on retry&lt;/th&gt;
&lt;th&gt;Final fake-server count: Qiita / dev.to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Both posts accepted and IDs saved&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;1 / 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qiita ID saved, then dev.to returns 422&lt;/td&gt;
&lt;td&gt;Qiita PATCH, dev.to POST&lt;/td&gt;
&lt;td&gt;1 / 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qiita accepts, then response is lost&lt;/td&gt;
&lt;td&gt;Qiita POST, dev.to POST&lt;/td&gt;
&lt;td&gt;2 / 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dev.to accepts, then response is lost&lt;/td&gt;
&lt;td&gt;Qiita PATCH, dev.to POST&lt;/td&gt;
&lt;td&gt;1 / 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qiita accepts, then local persistence fails&lt;/td&gt;
&lt;td&gt;Qiita POST, dev.to POST&lt;/td&gt;
&lt;td&gt;2 / 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A separate dry-run check produced zero API calls and zero fake-server articles. Assertions verified all 6 cases.&lt;/p&gt;

&lt;p&gt;This does not measure how often a real service loses a response. It checks which information the script has when choosing its next request. The fake server covers the Qiita and dev.to paths, not Zenn's Git integration or waiting inside actual GitHub jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recover the ID before deciding to retry
&lt;/h2&gt;

&lt;p&gt;The results support this proposed operational sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fix the target date and use dry-run to check the Japanese and English paths.&lt;/li&gt;
&lt;li&gt;Inspect the failed run's log and the IDs saved in the files.&lt;/li&gt;
&lt;li&gt;If a timeout or similar failure leaves acceptance uncertain, look for an existing post in the platform's article list or editor.&lt;/li&gt;
&lt;li&gt;Once the matching post is identified, recover its actual ID and status before rerunning.&lt;/li&gt;
&lt;li&gt;If creation cannot be established, stop automatic new POST attempts and continue investigating.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a proposed procedure for operating the existing script, not an implemented automatic reconciliation feature. Compare the body, target date, and canonical as well as the title. Never invent an ID.&lt;/p&gt;

&lt;p&gt;Dry-run is available through this command, replacing the date with the one under investigation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node scripts/publish-scheduled.mjs &lt;span class="nt"&gt;--date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2026-08-04 &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reported publication state comes from local frontmatter. A successful dry-run does not establish that there are no externally created posts missing from local records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start further automation at the persistence boundary
&lt;/h2&gt;

&lt;p&gt;One possible improvement is to record states such as not sent, acceptance unresolved, and ID saved. An unresolved acceptance could prevent another automatic creation request. A reconciliation procedure would still be required.&lt;/p&gt;

&lt;p&gt;The current implementation does not have this mechanism. More local state does not remove the interval in which the external service has created an article but its ID has been lost. Whether the service supports idempotent creation or a reliable way to locate the result needs separate investigation.&lt;/p&gt;

&lt;p&gt;In the tested code, failures after the ID is saved can be retried as updates. Failures after external acceptance but before ID persistence can return to a new POST. Recovery should distinguish those cases instead of treating both as a single "failed" state.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>javascript</category>
      <category>writing</category>
      <category>testing</category>
    </item>
    <item>
      <title>Reusing a game jam repository starts with event boundaries and explicit export targets</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Tue, 22 Sep 2026 02:02:09 +0000</pubDate>
      <link>https://dev.to/hirodeath/reusing-a-game-jam-repository-starts-with-event-boundaries-and-explicit-export-targets-3fbd</link>
      <guid>https://dev.to/hirodeath/reusing-a-game-jam-repository-starts-with-event-boundaries-and-explicit-export-targets-3fbd</guid>
      <description>&lt;p&gt;After a game jam, source code is only part of what is worth keeping. The procedure for building the submission ZIP, the export preset, and the test command are useful material for the next event too.&lt;/p&gt;

&lt;p&gt;Keeping an old game and building a reusable library are different amounts of work. An inspection of &lt;code&gt;game-jam-lab&lt;/code&gt; found an event-based structure containing documents and tools, but not a completed SDK shared by every event.&lt;/p&gt;

&lt;p&gt;This article examines a design that leaves room for more events while making the target of each test and export explicit. The inspected version is commit &lt;code&gt;f074703&lt;/code&gt;, checked on September 11, 2026. Ongoing uncommitted work is not counted as completed functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the submission's parts together
&lt;/h2&gt;

&lt;p&gt;The relevant parts of the &lt;a href="https://github.com/takahiro-saeki/game-jam-lab/tree/f074703848586828b6a5acc0e465ccdd2c0d5244/events/2026-ai-browser-game-jam-4" rel="noopener noreferrer"&gt;committed structure&lt;/a&gt; look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;events/
  2026-ai-browser-game-jam-4/
    godot/
      project.godot
      shared/
      tests/
    docs/
    tools/
    submission/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This commit tracks one event. The structure is being evaluated as a place to add the next event, not as evidence of completed operations across multiple events.&lt;/p&gt;

&lt;p&gt;Keeping the game, design notes, tools, and submission material under the event makes the intended submission traceable from a path. Compared with mixing several games' &lt;code&gt;project.godot&lt;/code&gt; files and ZIP-building logic at the repository root, it gives the next event a clear home while preserving the previous one.&lt;/p&gt;

&lt;p&gt;Copying a folder does not necessarily prepare the next submission, though. The event name, game name, export preset, and publication destination still need checking. Separate storage does not automatically switch those settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolve the target from the script's own location
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/game-jam-lab/blob/f074703848586828b6a5acc0e465ccdd2c0d5244/events/2026-ai-browser-game-jam-4/tools/build-itch-project-charge.sh" rel="noopener noreferrer"&gt;itch.io export script&lt;/a&gt; derives the event root from its own location, rather than the caller's current working directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;script_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;event_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$script_dir&lt;/span&gt;&lt;span class="s2"&gt;/.."&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;godot_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$event_root&lt;/span&gt;&lt;span class="s2"&gt;/godot"&lt;/span&gt;
&lt;span class="nv"&gt;output_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$event_root&lt;/span&gt;&lt;span class="s2"&gt;/build/itch/volt-nomad"&lt;/span&gt;
&lt;span class="nv"&gt;archive_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$event_root&lt;/span&gt;&lt;span class="s2"&gt;/build/itch/volt-nomad-web.zip"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It resolves &lt;code&gt;godot_root&lt;/code&gt; before passing it to &lt;code&gt;--path&lt;/code&gt;. Output also lives under that event's &lt;code&gt;build/&lt;/code&gt; directory. Running the script from the repository root or inside the event therefore targets the same project.&lt;/p&gt;

&lt;p&gt;This does not make the script ready for the next event without changes. The output name &lt;code&gt;volt-nomad&lt;/code&gt; and preset &lt;code&gt;Itch Volt Nomad&lt;/code&gt; remain specific to this game. They need attention when copying the script. Before generalizing the script, make its game-specific values easy to identify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test path resolution and ZIP layout separately
&lt;/h2&gt;

&lt;p&gt;To examine this behavior, the actual script from the fixed commit was extracted into a temporary directory. The event directory was renamed for the fixture, and a small stub replaced the Godot executable.&lt;/p&gt;

&lt;p&gt;The stub records its arguments and creates fixture files named &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;index.js&lt;/code&gt;, &lt;code&gt;index.pck&lt;/code&gt;, and &lt;code&gt;index.wasm&lt;/code&gt;. It does not export a game through Godot. The original shell code still creates the ZIP and checks its required files.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Working directory&lt;/th&gt;
&lt;th&gt;Target passed to &lt;code&gt;--path&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Four files at ZIP root&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixture repository root&lt;/td&gt;
&lt;td&gt;That event's &lt;code&gt;godot/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;All present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;That event's &lt;code&gt;godot/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;That event's &lt;code&gt;godot/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;All present&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The script also passed &lt;code&gt;bash -n&lt;/code&gt;. The &lt;a href="https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/experiments/article-stock-2026-09/game-jam-layout.mjs" rel="noopener noreferrer"&gt;verification code&lt;/a&gt; ran with Node.js &lt;code&gt;v24.15.0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node experiments/article-stock-2026-09/game-jam-layout.mjs ../game-jam-lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This verifies path construction and ZIP layout. Game startup, a successful Web export, and running on itch.io require separate checks. The inspected CI specifies Godot &lt;code&gt;4.6.2&lt;/code&gt;, but this experiment did not execute Godot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the scope of &lt;code&gt;shared/&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The event's &lt;code&gt;godot/shared/&lt;/code&gt; directory contains files for controller input, palettes, audio settings, and synthesis. They live inside the event. They are not a repository-level common &lt;code&gt;tools/&lt;/code&gt; directory or a library extracted for use across events.&lt;/p&gt;

&lt;p&gt;That scope matters when reusing code in the next game. Code shared within an event can reasonably know the names and settings of that event's games. Moving it elsewhere requires checking dependencies such as input action names and save locations.&lt;/p&gt;

&lt;p&gt;A possible next step is to copy the code into the next event and inspect the resulting differences before moving it into a common library. If the game name is the only recurring change, an argument or setting might be enough. If progression logic and assets must also be rewritten, keeping that part inside the event may remain easier to understand.&lt;/p&gt;

&lt;p&gt;The evidence for extraction is what two events can use unchanged, not the directory name &lt;code&gt;shared&lt;/code&gt;. That comparison has not yet been performed here, so there is no measured time saving to report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding an event does not select it for CI
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/game-jam-lab/blob/f074703848586828b6a5acc0e465ccdd2c0d5244/.github/workflows/pages.yml" rel="noopener noreferrer"&gt;Pages workflow&lt;/a&gt; hardcodes &lt;code&gt;events/2026-ai-browser-game-jam-4/godot&lt;/code&gt; as the &lt;code&gt;--path&lt;/code&gt; for smoke tests and Web export. It does not discover and run every directory under &lt;code&gt;events/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Adding the next event's folder therefore does not automatically test the next game. The workflow needs an explicit choice of which projects to test, which to publish, and which merely to preserve.&lt;/p&gt;

&lt;p&gt;A future design could separate a list of verification targets from a single publication target. Checking older events for syntax or startup problems and deciding whether a new game should replace the published build are separate decisions. This design is not implemented in the inspected commit.&lt;/p&gt;

&lt;p&gt;To leave the repository usable for another event, preserve the event's parts and inspect what its scripts and CI point to. In this structure, the export script's relative placement can carry forward. The game-specific output names and the fixed CI target are choices to revisit for the next event.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>githubactions</category>
      <category>programming</category>
    </item>
    <item>
      <title>Reviewing AI-generated code by breaking one invariant and reading the failing test</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Mon, 21 Sep 2026 01:54:27 +0000</pubDate>
      <link>https://dev.to/hirodeath/reviewing-ai-generated-code-by-breaking-one-invariant-and-reading-the-failing-test-2c9f</link>
      <guid>https://dev.to/hirodeath/reviewing-ai-generated-code-by-breaking-one-invariant-and-reading-the-failing-test-2c9f</guid>
      <description>&lt;p&gt;An AI assistant returns a diff and tests, and every test passes. The awkward moment comes when you still cannot explain, in your own words, what those tests protect.&lt;/p&gt;

&lt;p&gt;One review technique is to choose an invariant, deliberately break it, and check whether a test fails. The failing expectation points to the condition it protects in the implementation.&lt;/p&gt;

&lt;p&gt;For this article, SquadNote's real notification router and existing tests were extracted into a temporary copy. Removing one deletion condition changed the result from eight passing tests to seven passing tests and one failure. The check ran on September 11, 2026, using Node.js &lt;code&gt;v24.15.0&lt;/code&gt; and Vitest &lt;code&gt;4.1.4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This experiment does not establish who wrote the code. It presents a review procedure that also applies to code received from an AI assistant. It describes the procedure tested here, without claiming benefits from continued use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn “logout works” into a condition about stored data
&lt;/h2&gt;

&lt;p&gt;The example deletes a device's push token. Consider a device that switches from account A to account B, followed by a delayed deregistration request from A.&lt;/p&gt;

&lt;p&gt;“Logout works” leaves too much room for interpretation. The invariant here is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A request from former owner A must not delete a device token that has moved to B.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This identifies the actor, the target, and the data that must survive. It translates into a test more readily than “handle authorization correctly.”&lt;/p&gt;

&lt;p&gt;The relevant deletion predicate is:&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="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pushTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pushTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&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 token must match, and its current owner must match the session user. Registration and ownership transfer form a larger topic; this review stays focused on the deletion predicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the counterexample before changing the code
&lt;/h2&gt;

&lt;p&gt;Start with the sequence of operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A registers &lt;code&gt;ios-1&lt;/code&gt; and &lt;code&gt;android-1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;B registers &lt;code&gt;android-1&lt;/code&gt;, transferring that token's ownership to B.&lt;/li&gt;
&lt;li&gt;A requests deregistration of &lt;code&gt;android-1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;B's &lt;code&gt;android-1&lt;/code&gt; and A's &lt;code&gt;ios-1&lt;/code&gt; must remain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/circle-hub/blob/0cda1e865ad80d1529197729d8d436e96d56edc7/apps/web/src/security/push-token-privacy.test.ts" rel="noopener noreferrer"&gt;existing test&lt;/a&gt; already follows this sequence. It reads the database rows afterward instead of merely checking whether an API was called. Its final expectation is:&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="nf"&gt;expect&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;rows&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;android-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;android&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="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ios-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ios&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;It also matters where &lt;code&gt;rows()&lt;/code&gt; reads from. These tests use the actual router and a libSQL test database, with external HTTP mocked. If &lt;code&gt;unregisterPushToken&lt;/code&gt; itself were replaced by a mock that always succeeded, the test would not exercise this predicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remove only the user condition
&lt;/h2&gt;

&lt;p&gt;The experiment extracted commit &lt;code&gt;0cda1e8&lt;/code&gt; into a temporary directory and ran the original tests. It then changed the predicate, only in that copy, to:&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="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pushTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an injected fault for examining the tests. It is not a proposed production change. Deleting by token alone should allow A's stale request to remove B's registration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Passed&lt;/th&gt;
&lt;th&gt;Failed&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original deletion predicate&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Existing expectations hold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User condition removed&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Deregistration after account transfer is detected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The failing test was the one asserting that switching accounts transfers only that device and that the previous owner cannot delete it. Its expectation about the stored rows no longer matched.&lt;/p&gt;

&lt;p&gt;A test that was already failing would not establish this difference. A syntax error that prevented the suite from starting would not establish that the intended invariant was detected either. Here, the failure occurred at the test's data expectation.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/experiments/article-stock-2026-09/mutation-check.mjs" rel="noopener noreferrer"&gt;experiment script&lt;/a&gt; extracts the fixed commit, runs the baseline, removes the condition, and runs the tests again without modifying the original repository. With the source project's dependencies already installed, run it from the article repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node experiments/article-stock-2026-09/mutation-check.mjs ../circle-hub-multi-device-push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What to inspect if the test still passes
&lt;/h2&gt;

&lt;p&gt;This counterexample was detected. If a different mutation leaves a suite green, first check whether the counterexample reaches the intended code before adding more tests.&lt;/p&gt;

&lt;p&gt;For example, a mock that always supplies user A cannot establish a transfer to B. An assertion that checks only a successful response, without reading the rows afterward, can miss excessive deletion. The test might also exercise a different router from the one you changed.&lt;/p&gt;

&lt;p&gt;Read the input setup, the invoked function, the mocks at external boundaries, and the final expectations in that order. Once you know which components actually run, add the missing counterexample.&lt;/p&gt;

&lt;p&gt;This procedure establishes that a chosen test detects a chosen fault. It does not prove the absence of every authorization bug, every problematic concurrent ordering, or failures in delivery to a real device. This experiment did not use external delivery or physical devices.&lt;/p&gt;

&lt;p&gt;When asking an AI assistant for an explanation, “Which data would disappear if this condition were removed, and which expectation would fail?” produces an answer you can compare with execution. Record the invariant, the counterexample, and the expectation that actually failed. That record helps the next reviewer decide whether the same condition can be removed during a cleanup.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Comparing Promise.all, allSettled, any, and race when failure arrives first</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Sun, 20 Sep 2026 01:53:35 +0000</pubDate>
      <link>https://dev.to/hirodeath/comparing-promiseall-allsettled-any-and-race-when-failure-arrives-first-3bec</link>
      <guid>https://dev.to/hirodeath/comparing-promiseall-allsettled-any-and-race-when-failure-arrives-first-3bec</guid>
      <description>&lt;p&gt;When work can partially fail, decide which event should settle the aggregate Promise. Replacing &lt;code&gt;Promise.all&lt;/code&gt; with &lt;code&gt;allSettled&lt;/code&gt; does not change how many operations start or how they can be cancelled.&lt;/p&gt;

&lt;p&gt;The comparison passes A, B, and C in that order, then rejects B, fulfills C, and finally fulfills A.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Immediately after B fails&lt;/th&gt;
&lt;th&gt;Result observed after all inputs settle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;td&gt;B's error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allSettled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pending&lt;/td&gt;
&lt;td&gt;An array: A fulfilled, B rejected, C fulfilled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;any&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pending&lt;/td&gt;
&lt;td&gt;C's value, the first fulfillment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;race&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;td&gt;B's error, the first settlement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The test environment was Node.js &lt;code&gt;v24.15.0&lt;/code&gt; on September 11, 2026. This checks a controlled settlement order, not wall-clock speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specify completion order without timers
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Promise.withResolvers()&lt;/code&gt; returns a Promise together with externally callable &lt;code&gt;resolve&lt;/code&gt; and &lt;code&gt;reject&lt;/code&gt; functions. The core of the comparison is:&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="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withResolvers&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withResolvers&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;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withResolvers&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;C result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no real network request here. The result follows the input order A, B, C, rather than the completion order B, C, A. A batch can use that ordering to associate each result with its original input.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/experiments/article-stock-2026-09/promise-combinators.mjs" rel="noopener noreferrer"&gt;complete reproduction&lt;/a&gt; runs the four methods separately and asserts both their state after B's failure and their final result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node experiments/article-stock-2026-09/promise-combinators.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settlement rules were also checked against the &lt;a href="https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise.all" rel="noopener noreferrer"&gt;ECMAScript Promise specification&lt;/a&gt;. This comparison supplies an array of ordinary Promises; exceptions thrown by the input iterator itself are a separate case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The remaining inputs still exist after rejection
&lt;/h2&gt;

&lt;p&gt;After &lt;code&gt;all&lt;/code&gt; and &lt;code&gt;race&lt;/code&gt; rejected with B's error, the experiment could still fulfill C and A. Rejection of the aggregate did not cancel the input Promises.&lt;/p&gt;

&lt;p&gt;In a notification batch, some requests might already have been sent. Resending every item simply because execution reached &lt;code&gt;all&lt;/code&gt;'s catch handler could duplicate successful sends. Aggregating results and deciding how to stop or retry individual operations need separate policies.&lt;/p&gt;

&lt;p&gt;The same applies to &lt;code&gt;race&lt;/code&gt;. If a timeout Promise settles first, the competing network operation is not automatically stopped. Cancellation requires a mechanism supported by the operation itself. Implementing cancellable fetches or timers is outside this experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  A synchronous throw can happen before allSettled receives anything
&lt;/h2&gt;

&lt;p&gt;This version throws while constructing the array:&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="nx"&gt;jobs&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="o"&gt;=&amp;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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync failure&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;job&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;map&lt;/code&gt; never finishes, execution never reaches the call to &lt;code&gt;allSettled&lt;/code&gt;. To collect failures from every job as results, move each invocation into a Promise:&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="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&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 resulting statuses were &lt;code&gt;fulfilled&lt;/code&gt;, &lt;code&gt;rejected&lt;/code&gt;, and &lt;code&gt;fulfilled&lt;/code&gt;. This is not special handling of synchronous exceptions by &lt;code&gt;allSettled&lt;/code&gt;: the exception inside &lt;code&gt;then&lt;/code&gt; becomes rejection of that input Promise.&lt;/p&gt;

&lt;p&gt;It still does not impose a concurrency limit. It only moves function execution into Promises. If a large set of jobs must not start together, choose a worker count or queue separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Include empty input in the caller's contract
&lt;/h2&gt;

&lt;p&gt;For an empty array, &lt;code&gt;all&lt;/code&gt; and &lt;code&gt;allSettled&lt;/code&gt; fulfill with an empty array. &lt;code&gt;any&lt;/code&gt; rejects with an &lt;code&gt;AggregateError&lt;/code&gt; whose &lt;code&gt;errors&lt;/code&gt; array is empty, and &lt;code&gt;race&lt;/code&gt; stays pending. The same script checks these cases.&lt;/p&gt;

&lt;p&gt;For example, passing an empty list of candidate servers straight to &lt;code&gt;race&lt;/code&gt; will not produce a result. Decide in advance whether zero inputs mean success or an invalid request.&lt;/p&gt;

&lt;p&gt;In this comparison, &lt;code&gt;allSettled&lt;/code&gt; collects every outcome; &lt;code&gt;all&lt;/code&gt; requires every input to succeed; &lt;code&gt;any&lt;/code&gt; needs one success; and &lt;code&gt;race&lt;/code&gt; observes the first settlement regardless of success or failure. None of those choices alone determines persistence or cancellation behavior.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
      <category>testing</category>
    </item>
    <item>
      <title>Choose Next.js Server Actions or Route Handlers by the contract with the caller</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Sat, 19 Sep 2026 01:50:47 +0000</pubDate>
      <link>https://dev.to/hirodeath/choose-nextjs-server-actions-or-route-handlers-by-the-contract-with-the-caller-2dnc</link>
      <guid>https://dev.to/hirodeath/choose-nextjs-server-actions-or-route-handlers-by-the-contract-with-the-caller-2dnc</guid>
      <description>&lt;p&gt;When writing a server-side mutation in Next.js, the same business logic can appear to fit inside either a Server Action or a Route Handler. Memorizing “Actions for forms, Routes for APIs” becomes less helpful when a mobile app or a cron job needs to invoke it too.&lt;/p&gt;

&lt;p&gt;The choice starts with the contract you want to give the caller. An Action is a candidate when the operation belongs to a Next.js screen, including its navigation flow. A Route Handler fits when you want to explain a URL, HTTP method, authentication header, and JSON result to another caller.&lt;/p&gt;

&lt;p&gt;SquadNote's form and cron endpoint provide concrete examples. The inspected source is commit &lt;code&gt;0cda1e8&lt;/code&gt;, with Next.js &lt;code&gt;15.5.15&lt;/code&gt; and React &lt;code&gt;19.2.5&lt;/code&gt; installed locally. The official documentation consulted on September 11, 2026, displayed version &lt;code&gt;16.3.4&lt;/code&gt;. The current documentation and the version actually tested are recorded separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The form passes the authentication operation and return destination together
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/circle-hub/blob/0cda1e865ad80d1529197729d8d436e96d56edc7/apps/web/src/app/%28app%29/settings/_components/linked-accounts.tsx" rel="noopener noreferrer"&gt;account-linking screen&lt;/a&gt; contains code equivalent to this shortened excerpt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;async &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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use server&lt;/span&gt;&lt;span class="dl"&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;signIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/settings&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="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Connect&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Styling and other button details are omitted. Here, &lt;code&gt;p&lt;/code&gt; is an authentication provider enumerated by the screen. The form starts that provider's authentication flow and specifies a return destination appropriate for the screen.&lt;/p&gt;

&lt;p&gt;This Action is the entry point for a screen operation. It does not need to define a JSON format for a separate client.&lt;/p&gt;

&lt;p&gt;The official &lt;a href="https://nextjs.org/docs/app/getting-started/mutating-data" rel="noopener noreferrer"&gt;Mutating Data guide&lt;/a&gt; describes a Server Function used in a form or mutation context as a Server Action. Its transport uses POST, and it can also receive direct POST requests outside the application's UI. A button being visible only on one screen does not make that screen's permission check sufficient.&lt;/p&gt;

&lt;p&gt;The excerpt starts authentication; it is not an authorization template for arbitrary mutations. Replacing it with post deletion, for example, requires checking the acting user and their relationship to the post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cron endpoint has HTTP results to return
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/takahiro-saeki/circle-hub/blob/0cda1e865ad80d1529197729d8d436e96d56edc7/apps/web/src/app/api/cron/morning-reminder/route.ts" rel="noopener noreferrer"&gt;morning notification route&lt;/a&gt; exports &lt;code&gt;POST(request: Request)&lt;/code&gt;. Its sequence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/cron/morning-reminder
  Read the configured secret
  Missing configuration → 500
  Authorization header mismatch → 401
  Query today's schedules
  Send push notifications if there are recipients
  Return a JSON result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron caller needs to know which method to use, which header to attach, and which result to expect. Passing a function to a form is not its interface. A &lt;a href="https://nextjs.org/docs/app/getting-started/route-handlers" rel="noopener noreferrer"&gt;Route Handler&lt;/a&gt;, built around the Web Request and Response APIs, fits that need.&lt;/p&gt;

&lt;p&gt;For the same reason, defining an HTTP contract helps when an endpoint will serve mobile apps or external services. Choosing a Route Handler does not, by itself, specify authentication or the behavior of repeated requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify that rejected requests stop before database access
&lt;/h2&gt;

&lt;p&gt;The route from the fixed commit was extracted into a temporary directory, and its exported &lt;code&gt;POST&lt;/code&gt; function was invoked directly. Database access and push delivery were mocked to check that execution stopped before reaching them when authentication conditions were unmet.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Database accessor&lt;/th&gt;
&lt;th&gt;Push delivery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No configured secret&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;Not called&lt;/td&gt;
&lt;td&gt;Not called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret configured, header absent&lt;/td&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;Not called&lt;/td&gt;
&lt;td&gt;Not called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret configured, header mismatched&lt;/td&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;Not called&lt;/td&gt;
&lt;td&gt;Not called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matching header, mocked query returns zero schedules&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;Called&lt;/td&gt;
&lt;td&gt;Not called&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All four tests passed on Node.js &lt;code&gt;v24.15.0&lt;/code&gt;, Vitest &lt;code&gt;4.1.4&lt;/code&gt;, and Next.js &lt;code&gt;15.5.15&lt;/code&gt;. The &lt;a href="https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/experiments/article-stock-2026-09/next-route-entry.mjs" rel="noopener noreferrer"&gt;verification script&lt;/a&gt; accepts the source repository with its dependencies already installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node experiments/article-stock-2026-09/next-route-entry.mjs ../circle-hub-multi-device-push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exercises the actual route function without starting a Next.js HTTP server. It uses the local environment-variable fallback. Cloudflare binding access, cron triggering, network requests, and real notification delivery are outside the test. The four cases do not test the Action transport either.&lt;/p&gt;

&lt;p&gt;Even within that scope, invoking the entry point makes its conditions for reaching business logic observable. A test that checks only a 200 response would not reveal a missing rejection branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reuse the logic behind the entry points
&lt;/h2&gt;

&lt;p&gt;Suppose the same mutation later needs to serve both an admin screen and a mobile app. Instead of treating the Action as the external client's API specification, consider separating the common business function from its entry points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin UI → Action → Session check ─┐
                                 ├→ Resource permission and input checks → Mutation
Mobile   → Route → API auth ──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a proposed structure, not a refactoring completed on SquadNote's two entry points. The common function can receive an actor verified by server code or obtain the actor itself. A &lt;code&gt;userId&lt;/code&gt; supplied in form data or JSON must not become a trusted actor merely because it was passed to that function.&lt;/p&gt;

&lt;p&gt;If business authorization moves into the shared layer, every entry point must pass through it. The Action can retain form conversion and UI updates; the Route can retain HTTP input conversion and status selection.&lt;/p&gt;

&lt;p&gt;The official &lt;a href="https://nextjs.org/docs/app/guides/data-security" rel="noopener noreferrer"&gt;Data Security guide&lt;/a&gt; also explains that a page-level authentication check does not extend to Actions defined inside the page. It shows authentication and authorization centralized in a data access layer invoked by the Action. A &lt;code&gt;use server&lt;/code&gt; directive alone does not verify permission to operate on a resource.&lt;/p&gt;

&lt;p&gt;Before choosing an entry point, write down the caller, credentials, input, and success and failure results. Choose an Action when these fit naturally into a screen operation, or a Route Handler when they need an independent HTTP contract. If reuse becomes necessary, extract the business logic while preserving those contracts.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Choosing Plane Projects, Modules, Cycles, and Initiatives by what needs to be finished</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Fri, 18 Sep 2026 01:46:03 +0000</pubDate>
      <link>https://dev.to/hirodeath/choosing-plane-projects-modules-cycles-and-initiatives-by-what-needs-to-be-finished-1666</link>
      <guid>https://dev.to/hirodeath/choosing-plane-projects-modules-cycles-and-initiatives-by-what-needs-to-be-finished-1666</guid>
      <description>&lt;p&gt;Trying to use every organizational level in Plane creates extra containers even when one person is building one feature. A more useful selection rule is to ask what kind of completion you need to check.&lt;/p&gt;

&lt;p&gt;Use a Project for ongoing service maintenance, a Module for a feature or release, a Cycle for work within a period, and an Initiative for an objective spanning Projects. These do not need to form a mandatory linear hierarchy.&lt;/p&gt;

&lt;p&gt;This is a design proposal based on Plane's official documentation checked on September 11, 2026, and SquadNote's multi-device push implementation. The inspected Workspace had its dedicated Initiatives feature disabled. SquadNote had zero non-archived Modules and zero non-archived Cycles. The arrangements below are proposals, not established operating practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not copy one change into four separate tickets
&lt;/h2&gt;

&lt;p&gt;Multi-device push involves token registration, logout, recipient selection, and device reception checks. The repository collects its requirements in &lt;code&gt;docs/multi-device-push.md&lt;/code&gt;, linked to &lt;code&gt;SQN-49&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using that work as an example, each organizational unit answers a different question.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Possible arrangement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;Which service owns this work?&lt;/td&gt;
&lt;td&gt;SquadNote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module&lt;/td&gt;
&lt;td&gt;Which deliverable must be complete?&lt;/td&gt;
&lt;td&gt;Multi-device push support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cycle&lt;/td&gt;
&lt;td&gt;What will move forward during this period?&lt;/td&gt;
&lt;td&gt;Registration API and logout implementation during a week in September&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initiative&lt;/td&gt;
&lt;td&gt;What should several Projects achieve together?&lt;/td&gt;
&lt;td&gt;Review notification operations across multiple apps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last example is hypothetical. A change confined to SquadNote does not need an Initiative.&lt;/p&gt;

&lt;p&gt;The design should not duplicate the same work into separate tickets for the Project, Module, and Cycle. One could become Done while the others retain stale states. Establish one Work Item for the actual work, then associate it with the organizational units that are useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Project as the ongoing maintenance home
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.plane.so/core-concepts/projects/overview" rel="noopener noreferrer"&gt;official Project documentation&lt;/a&gt; places Work Items, Cycles, and Modules inside a Project.&lt;/p&gt;

&lt;p&gt;SquadNote will still need authentication and attendance maintenance after its push feature is complete. Finishing this feature and closing the entire Project are therefore different events.&lt;/p&gt;

&lt;p&gt;"Is multi-device push finished?" and "Does SquadNote have unfinished work?" are separate questions. A single status cannot usefully answer both. The Project can remain in progress after a feature is finished because it is the continuing home for that product's work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give a Module deliverable-level acceptance criteria
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.plane.so/core-concepts/modules" rel="noopener noreferrer"&gt;Module documentation&lt;/a&gt; describes grouping Work Items around features and other bodies of work. Modules can have start and due dates, and a Work Item can belong to several Modules.&lt;/p&gt;

&lt;p&gt;If multi-device push becomes a Module, "improve notifications" would be too vague to establish completion. The implementation document supports more concrete criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preserve iOS and Android tokens for the same user at the same time.&lt;/li&gt;
&lt;li&gt;Logging out on one device leaves the other device's registration intact.&lt;/li&gt;
&lt;li&gt;Sending targets registered devices belonging to users with notifications enabled.&lt;/li&gt;
&lt;li&gt;Record actual reception on the owner's test devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Completing only the first item should not close the Module. Saving a token on the server and receiving a notification on a device are different results.&lt;/p&gt;

&lt;p&gt;If the feature also needs to be tracked as part of an application release, a feature Module and a release Module are an option. But a change small enough for one Work Item need not have a Module at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Cycle's end date is not necessarily a feature's completion date
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.plane.so/core-concepts/cycles" rel="noopener noreferrer"&gt;Cycle documentation&lt;/a&gt; describes a time-bounded set of work. Unfinished Work Items can remain when the period ends and can be transferred to another Cycle.&lt;/p&gt;

&lt;p&gt;Suppose registration and logout change this week, while device reception is checked next week. This week's Cycle ends, but the multi-device push acceptance criteria are still incomplete.&lt;/p&gt;

&lt;p&gt;Closing the feature Module just because the Cycle ended would hide the pending device checks. Keep the Module's criteria and plan the unfinished verification in the next period.&lt;/p&gt;

&lt;p&gt;Conversely, the fact that Modules can have due dates does not mean every weekly plan needs to be a Module. Choose based on whether you are naming a deliverable or deciding what to work on during a period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consider an Initiative when there is a shared objective
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.plane.so/core-concepts/projects/initiatives" rel="noopener noreferrer"&gt;Initiatives documentation&lt;/a&gt; describes tracking related Projects under a common objective. The page labels the feature Pro and provides instructions for enabling it.&lt;/p&gt;

&lt;p&gt;The dedicated feature was disabled in the inspected personal Workspace. The MCP response also mentioned a Work Item type with the same name, but that is not evidence that the dedicated Initiatives feature is in use. Matching names do not establish matching capabilities or relationships.&lt;/p&gt;

&lt;p&gt;As a design example, an objective as broad as "work on personal development" would not tell you which Projects to include or when to finish. "Standardize notification deregistration for the selected apps and complete reception checks for each" makes it possible to list the Projects and acceptance criteria. This illustrates when a shared objective could help; it does not claim that this cross-application work was performed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one Work Item
&lt;/h2&gt;

&lt;p&gt;SquadNote's Project and a concrete Work Item already lead to its implementation document. Work was not blocked by the absence of Modules or Cycles.&lt;/p&gt;

&lt;p&gt;Start with acceptance criteria in one Work Item. Consider a Module when related tasks grow and the deliverable needs its own completion view. Use a Cycle when planning and carryover across weeks become useful. Consider an Initiative once a specific objective requires judging several Projects together. This sequence is a proposal for the personal-development case examined here.&lt;/p&gt;

&lt;p&gt;The effect of additional grouping on completion or sustained use has not been compared. A useful starting check is whether a ticket can clearly express this state: the API changes are complete, while reception on devices remains to be verified.&lt;/p&gt;

&lt;p&gt;Implementation source: &lt;a href="https://github.com/takahiro-saeki/circle-hub/blob/0cda1e865ad80d1529197729d8d436e96d56edc7/docs/multi-device-push.md" rel="noopener noreferrer"&gt;multi-device push requirements and verification criteria&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>opensource</category>
      <category>planning</category>
    </item>
    <item>
      <title>Keeping Codex decisions in the repository and checking them against implementation evidence</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Thu, 17 Sep 2026 01:58:33 +0000</pubDate>
      <link>https://dev.to/hirodeath/keeping-codex-decisions-in-the-repository-and-checking-them-against-implementation-evidence-46m0</link>
      <guid>https://dev.to/hirodeath/keeping-codex-decisions-in-the-repository-and-checking-them-against-implementation-evidence-46m0</guid>
      <description>&lt;p&gt;During a long production effort, finding an old proposal is only part of the problem. You also need to know whether it was adopted, remains a target, or was superseded.&lt;/p&gt;

&lt;p&gt;VOLT NOMAD's production documents include a decision log, plans for individual phases, and a release-candidate audit. Reading them at the same Git revision separates targets from verification results. This provides a concrete way to establish the current situation without relying on the last statement in a chat.&lt;/p&gt;

&lt;p&gt;The source is &lt;code&gt;events/2026-ai-browser-game-jam-4/&lt;/code&gt; in &lt;code&gt;game-jam-lab&lt;/code&gt;. Its documents and history at commit &lt;code&gt;f074703&lt;/code&gt; were inspected on September 11, 2026. An earlier production article covered the overall process; this one asks whether the next person can reconstruct a particular decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The original log records a choice and its reason
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CHARGE_CLICKER_PRODUCTION_PLAN.md&lt;/code&gt; has a table of dates, decisions, and reasons. One entry dated August 2, 2026, chooses verification stages of 30 seconds, 5 minutes, and 20 minutes. Its stated reason is to avoid hiding a weak core loop behind more content.&lt;/p&gt;

&lt;p&gt;When adding many features or assets becomes tempting, that entry provides the earlier criterion: establish that a short play session works first.&lt;/p&gt;

&lt;p&gt;But the same table's targets of roughly 20 minutes for the normal ending and roughly one hour for the true ending remain targets. Being written down does not turn them into measurements.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document statement&lt;/th&gt;
&lt;th&gt;What it establishes&lt;/th&gt;
&lt;th&gt;What it does not establish&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A decision to verify in stages&lt;/td&gt;
&lt;td&gt;The chosen verification approach at that time&lt;/td&gt;
&lt;td&gt;Completion of every stage's tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targets of about 20 minutes and one hour&lt;/td&gt;
&lt;td&gt;The intended experience duration at that time&lt;/td&gt;
&lt;td&gt;A person's measured completion time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A date and reason&lt;/td&gt;
&lt;td&gt;When and why the decision was made&lt;/td&gt;
&lt;td&gt;That it remained unchanged afterward&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A decision history is not an automatically current status report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check completion against later records
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PROJECT_CHARGE_POLISH_PLAN.md&lt;/code&gt; in the same directory lists phase statuses such as implemented, music selection pending, and final QA in progress. Its stated update date is August 6.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RELEASE_CANDIDATE_AUDIT_2026-08-12.md&lt;/code&gt;, dated August 12, contains automated verification results. With the steady input model of 2 inputs per second, it records 15.58 minutes to the normal ending and 23.26 minutes for the complete route.&lt;/p&gt;

&lt;p&gt;Comparing the original one-hour target with 23.26 minutes and declaring a major reduction would be premature. The latter uses automated input. The audit explicitly excludes time spent reading dialogue, understanding the interface, and comparing upgrades.&lt;/p&gt;

&lt;p&gt;The relationship that can be reconstructed is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production plan: target duration of a person's experience
Release-candidate audit: automated result under a fixed input model
Human playthrough: a separate verification step
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The automated playthrough was not rerun for this investigation. These numbers come from the saved audit, not a new measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix a Git revision before reading across documents
&lt;/h2&gt;

&lt;p&gt;Reading whatever happens to be in each working-directory file can mix in unfinished edits. For a technical explanation or article, pin the revision first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git show f074703:events/2026-ai-browser-game-jam-4/docs/CHARGE_CLICKER_PRODUCTION_PLAN.md
git show f074703:events/2026-ai-browser-game-jam-4/docs/PROJECT_CHARGE_POLISH_PLAN.md
git show f074703:events/2026-ai-browser-game-jam-4/docs/RELEASE_CANDIDATE_AUDIT_2026-08-12.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To investigate changes to a document, narrow the history to that file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; events/2026-ai-browser-game-jam-4/docs/CHARGE_CLICKER_PRODUCTION_PLAN.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A commit date need not match a decision date in the document. Records can be added later. Check the text for the decision date and Git for changes to the record; neither should stand in for every event's timestamp.&lt;/p&gt;

&lt;p&gt;The inspection confirmed that initial policy, intermediate phase status, and later audit results exist in separate documents. A "pending" status from August 6 cannot simply be copied into September's list of unfinished work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Label the kind of fact a new entry records
&lt;/h2&gt;

&lt;p&gt;The existing decision log uses date, decision, and reason columns. A proposed extension would include status and evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Decision: verify short play sessions first&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Status: adopted policy
&lt;span class="p"&gt;-&lt;/span&gt; Decision date: 2026-08-02
&lt;span class="p"&gt;-&lt;/span&gt; Reason: avoid hiding a weak core loop behind more content
&lt;span class="p"&gt;-&lt;/span&gt; Evidence: decision log in CHARGE_CLICKER_PRODUCTION_PLAN.md
&lt;span class="p"&gt;-&lt;/span&gt; Verification result: record separately from adoption of the policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is derived from the existing record; the format has not already been adopted in the source files. Distinguishing a policy, implementation, and verification result does not require a new ADR management tool.&lt;/p&gt;

&lt;p&gt;Silently replacing an old target with a new result also erases context. Preserve the previous target, the result under the current conditions, and the next question to check. A README or similar entry point can direct readers who only need the current policy to the current document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping a chat is different from being able to resume work
&lt;/h2&gt;

&lt;p&gt;A chat can preserve the nuance behind a choice. There is no need to discard it. But when the next necessary decision exists only halfway through a conversation, someone still has to establish which code revision it applies to.&lt;/p&gt;

&lt;p&gt;Repository documents become stale too. These materials contain plans and audits from different dates. Their mere existence does not complete a handoff.&lt;/p&gt;

&lt;p&gt;On resuming work, establish what was chosen, what was implemented, and under which conditions it was checked. Preserve the files and commits that answer those questions. Do not reinterpret a target as a measurement or an intermediate plan as the latest completion report. This provides enough evidence to choose the next check without rereading every chat.&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://github.com/takahiro-saeki/game-jam-lab/blob/f074703848586828b6a5acc0e465ccdd2c0d5244/events/2026-ai-browser-game-jam-4/docs/CHARGE_CLICKER_PRODUCTION_PLAN.md" rel="noopener noreferrer"&gt;Production plan and decision log&lt;/a&gt;, &lt;a href="https://github.com/takahiro-saeki/game-jam-lab/blob/f074703848586828b6a5acc0e465ccdd2c0d5244/events/2026-ai-browser-game-jam-4/docs/RELEASE_CANDIDATE_AUDIT_2026-08-12.md" rel="noopener noreferrer"&gt;release-candidate audit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>documentation</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>What reversing a D1 composite index changes in the query plan</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Wed, 16 Sep 2026 01:54:43 +0000</pubDate>
      <link>https://dev.to/hirodeath/what-reversing-a-d1-composite-index-changes-in-the-query-plan-2gbo</link>
      <guid>https://dev.to/hirodeath/what-reversing-a-d1-composite-index-changes-in-the-query-plan-2gbo</guid>
      <description>&lt;p&gt;For a notification query using &lt;code&gt;WHERE user_id = ? AND created_at &amp;gt;= ?&lt;/code&gt;, indexes on &lt;code&gt;(user_id, created_at)&lt;/code&gt; and &lt;code&gt;(created_at, user_id)&lt;/code&gt; do not behave identically.&lt;/p&gt;

&lt;p&gt;In a local D1 comparison, the first plan showed both user and creation time as search constraints. The reversed index showed creation time alone. The experiment examines the planned search range. It does not measure response time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix the query before comparing indexes
&lt;/h2&gt;

&lt;p&gt;The example takes the user and creation-time fields from SquadNote's notification table and keeps only the necessary columns. It does not change an operational database or its indexes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consecutive integers stand in for timestamps. The fixture contains rows numbered 1 through 10,000, distributed across 100 users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'user-'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SELECT stays unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'user-42'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;9000&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;user_id&lt;/code&gt; supplies equality, while &lt;code&gt;created_at&lt;/code&gt; supplies a range and ordering. The question is which column order supports this query, not which column is more important in general.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execute against Wrangler's local database
&lt;/h2&gt;

&lt;p&gt;The experiment ran on September 11, 2026, using Node.js &lt;code&gt;v24.15.0&lt;/code&gt; and Wrangler &lt;code&gt;4.81.1&lt;/code&gt;. The &lt;a href="https://github.com/takahiro-saeki/articles/tree/codex/article-stock-2026-09/experiments/article-stock-2026-09/d1-index" rel="noopener noreferrer"&gt;SQL and configuration&lt;/a&gt; are included in the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler@4.81.1 d1 execute article-index-lab &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; experiments/article-stock-2026-09/d1-index/wrangler.jsonc &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; experiments/article-stock-2026-09/d1-index/compare.sql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--persist-to&lt;/span&gt; /tmp/article-stock-d1-index-lab &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL recreates the experimental table. &lt;code&gt;--local&lt;/code&gt; and a dedicated persistence directory keep this separate from production. The configured database ID is a dummy value for local use. The &lt;a href="https://developers.cloudflare.com/d1/wrangler-commands/#execute" rel="noopener noreferrer"&gt;official CLI reference&lt;/a&gt; documents &lt;code&gt;--local&lt;/code&gt; and &lt;code&gt;--persist-to&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An attempt to inspect the internal SQLite version with &lt;code&gt;sqlite_version()&lt;/code&gt; was rejected in this environment. The internal version was not obtained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare no index, equality first, and range first
&lt;/h2&gt;

&lt;p&gt;The experiment prefixes the SELECT with &lt;code&gt;EXPLAIN QUERY PLAN&lt;/code&gt; and swaps one index for the other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_user_created&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reversed definition is below. Both indexes were not installed together for the comparison.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_created_user&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting &lt;code&gt;detail&lt;/code&gt; fields were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No index:
SCAN notification
USE TEMP B-TREE FOR ORDER BY

(user_id, created_at):
SEARCH notification USING COVERING INDEX idx_user_created (user_id=? AND created_at&amp;gt;?)

(created_at, user_id):
SEARCH notification USING COVERING INDEX idx_created_user (created_at&amp;gt;?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index, the plan scans the table and uses a temporary B-tree for sorting. &lt;code&gt;(user_id, created_at)&lt;/code&gt; searches the time range within the selected user. The reversed index starts with the time range and applies the user condition as further filtering.&lt;/p&gt;

&lt;p&gt;Although the plan displays &lt;code&gt;created_at&amp;gt;?&lt;/code&gt;, both queries execute &lt;code&gt;created_at &amp;gt;= 9000&lt;/code&gt;. Their returned IDs were checked too.&lt;/p&gt;

&lt;p&gt;SQLite's &lt;a href="https://sqlite.org/queryplanner.html" rel="noopener noreferrer"&gt;Query Planning documentation&lt;/a&gt; explains ordering by the leftmost columns of a composite index. The difference here is whether equality fixes the first column before searching the time range within it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Covering does not establish equivalent search ranges
&lt;/h2&gt;

&lt;p&gt;Both indexed plans say &lt;code&gt;COVERING INDEX&lt;/code&gt;. The query selects only &lt;code&gt;id&lt;/code&gt;, and this table's &lt;code&gt;INTEGER PRIMARY KEY&lt;/code&gt; aliases rowid. Being able to retrieve the needed values from the index does not mean the two indexes search the same range.&lt;/p&gt;

&lt;p&gt;Do not conclude that reversing the index is equivalent simply because both plans are covering. Also read which constraints appear inside the parentheses after &lt;code&gt;SEARCH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cloudflare's &lt;a href="https://developers.cloudflare.com/d1/best-practices/use-indexes/" rel="noopener noreferrer"&gt;index documentation&lt;/a&gt; provides another starting point for checking an index against a query. Production rows read and billing effects were not measured here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check that the returned rows remain identical
&lt;/h2&gt;

&lt;p&gt;Both indexed variants returned 10 rows with an ID sum of 94,920. Assertions also verified this exact ID sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9942, 9842, 9742, 9642, 9542, 9442, 9342, 9242, 9142, 9042
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps check that the SELECT has not accidentally changed while improving its plan. Different sets can share a count and sum, so this small result was compared directly as an ordered list of IDs.&lt;/p&gt;

&lt;p&gt;The fixture has an artificial, even distribution. A production workload concentrated on particular users, or a query filtering only by time, needs its own assessment. The cost of maintaining indexes during writes was not compared either.&lt;/p&gt;

&lt;p&gt;For this query, putting equality on &lt;code&gt;user_id&lt;/code&gt; first produced a plan using both constraints for the search. For another query, compare the SELECT, query plan, and returned rows together instead of choosing by column names alone.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>database</category>
      <category>sqlite</category>
      <category>sql</category>
    </item>
    <item>
      <title>Tracing Android safe-area spacing to the components that apply insets</title>
      <dc:creator>hiro@</dc:creator>
      <pubDate>Tue, 15 Sep 2026 02:03:33 +0000</pubDate>
      <link>https://dev.to/hirodeath/tracing-android-safe-area-spacing-to-the-components-that-apply-insets-4m8f</link>
      <guid>https://dev.to/hirodeath/tracing-android-safe-area-spacing-to-the-components-that-apply-insets-4m8f</guid>
      <description>&lt;p&gt;Before changing bottom spacing, find which components consume &lt;code&gt;insets.bottom&lt;/code&gt;. A parent &lt;code&gt;SafeAreaView&lt;/code&gt; and a child's &lt;code&gt;paddingBottom&lt;/code&gt; can avoid the same area, potentially adding extra space even when the library reports correct values.&lt;/p&gt;

&lt;p&gt;This is an investigation procedure based on reading SquadNote's code, not a report of reproducing and fixing a specific spacing bug on a device. The source is &lt;code&gt;circle-hub&lt;/code&gt; commit &lt;code&gt;31e560d&lt;/code&gt;, using Expo SDK 54, React Native &lt;code&gt;0.81.5&lt;/code&gt;, and a &lt;code&gt;react-native-safe-area-context&lt;/code&gt; declaration of &lt;code&gt;~5.6.0&lt;/code&gt;. It was inspected on September 11, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate OS conditions from application configuration
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/develop/ui/views/layout/edge-to-edge" rel="noopener noreferrer"&gt;Android documentation&lt;/a&gt; describes enforced edge-to-edge display on Android 15 or later when the application targets SDK 35 or higher. The target SDK matters as well as the device's OS.&lt;/p&gt;

&lt;p&gt;SquadNote has &lt;code&gt;edgeToEdgeEnabled: true&lt;/code&gt; in its configuration. Finding that setting does not establish that interactive controls avoid the system bars. Follow both the configuration that extends the background to the edges and the code applying insets to interactive areas.&lt;/p&gt;

&lt;p&gt;System bars, display cutouts, and system gesture regions are also different categories. This React Native code uses the library's top and bottom insets. How those values change on a device needs separate checks with navigation mode and orientation controlled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distinguish the Provider from the View applying spacing
&lt;/h2&gt;

&lt;p&gt;The relevant structure of authenticated screens is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SafeAreaProvider
  View
    Header       → paddingTop: insets.top
    View / Stack → individual screens
    BottomNav    → paddingBottom: insets.bottom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having a &lt;code&gt;SafeAreaProvider&lt;/code&gt; is different from adding padding to every child screen. The library's official &lt;a href="https://appandflow.github.io/react-native-safe-area-context/api/safe-area-provider/" rel="noopener noreferrer"&gt;SafeAreaProvider&lt;/a&gt; and &lt;a href="https://appandflow.github.io/react-native-safe-area-context/api/safe-area-view/" rel="noopener noreferrer"&gt;SafeAreaView&lt;/a&gt; documentation separates providing values from applying them as spacing.&lt;/p&gt;

&lt;p&gt;BottomNav places the bottom inset outside a container with the navigation bar's own height.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;insets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSafeAreaInsets&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;paddingBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;insets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottomNavHeight&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Navigation items */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This excerpt keeps the layout from the implementation and omits the buttons and color definitions. Separating inner height from outer padding avoids changing the buttons' own height just to clear the bottom edge.&lt;/p&gt;

&lt;p&gt;The public route group instead has no Header or BottomNav and wraps its Stack in &lt;code&gt;SafeAreaView&lt;/code&gt;. Different route groups in the same application have different owners for safe-area spacing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace the same edge when investigating double application
&lt;/h2&gt;

&lt;p&gt;Suppose another &lt;code&gt;SafeAreaView&lt;/code&gt; covering the bottom edge is added inside an authenticated screen. Whether spacing actually overlaps depends on Provider placement and the screen's region. Component names alone do not prove duplication. Check whether parent and child are both configured to avoid the same bottom-edge region.&lt;/p&gt;

&lt;p&gt;For one target screen, collect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Information&lt;/th&gt;
&lt;th&gt;Where to inspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The region against which insets are measured&lt;/td&gt;
&lt;td&gt;Nearest SafeAreaProvider and its layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which edges are applied&lt;/td&gt;
&lt;td&gt;SafeAreaView's &lt;code&gt;edges&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual additions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;paddingTop&lt;/code&gt;, &lt;code&gt;paddingBottom&lt;/code&gt;, and margins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changes of region&lt;/td&gt;
&lt;td&gt;Providers and layouts for modals or different routes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual values and rendering&lt;/td&gt;
&lt;td&gt;Device insets, container boundaries, and button positions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In its normal additive mode, SafeAreaView adds insets to specified padding. For example, padding of 16 plus a bottom inset of 24 produces 40. Those are illustrative values, not measurements from a device in this investigation.&lt;/p&gt;

&lt;p&gt;Choose whether to remove an edge or manual padding based on which component owns that edge. Subtracting a fixed value whenever spacing looks too large leaves assumptions that may fail with a different navigation mode or orientation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat startup crashes separately from spacing
&lt;/h2&gt;

&lt;p&gt;The working copy that prompted the investigation is named &lt;code&gt;android-safe-area-hotfix&lt;/code&gt;. However, the &lt;a href="https://github.com/takahiro-saeki/circle-hub/commit/31e560d" rel="noopener noreferrer"&gt;actual fix&lt;/a&gt; changes the safe-area-context dependency declaration, lockfile, QA build configuration, and tests. It does not change inset or padding code.&lt;/p&gt;

&lt;p&gt;Rerunning its focused tests with Node.js &lt;code&gt;v24.15.0&lt;/code&gt; and Vitest &lt;code&gt;4.1.4&lt;/code&gt; passed both tests. They check the dependency declaration and QA configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nt"&gt;--filter&lt;/span&gt; @squadnote/mobile &lt;span class="nb"&gt;exec &lt;/span&gt;vitest run src/lib/android-native-runtime.test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing those tests does not establish correct screen spacing or guarantee that a native startup crash will not recur. If the app crashes before the screen opens, inspect the native error first. If the screen opens with misplaced content, trace where spacing is applied.&lt;/p&gt;

&lt;p&gt;Authenticated and public routes assign inset handling to different components. Select the target route, identify who handles its bottom edge, then compare that implementation with the values and positions on a device.&lt;/p&gt;

</description>
      <category>android</category>
      <category>reactnative</category>
      <category>expo</category>
    </item>
  </channel>
</rss>
