<?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: LkSvn</title>
    <description>The latest articles on DEV Community by LkSvn (@lksvn).</description>
    <link>https://dev.to/lksvn</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%2F205286%2Fd72b44d2-fe1e-45a9-acef-646a13cf818b.gif</url>
      <title>DEV Community: LkSvn</title>
      <link>https://dev.to/lksvn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lksvn"/>
    <language>en</language>
    <item>
      <title>A Rename Can Be a Migration in a Local-First Markdown System</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Tue, 11 Aug 2026 22:13:01 +0000</pubDate>
      <link>https://dev.to/lksvn/a-rename-can-be-a-migration-in-a-local-first-markdown-system-2bld</link>
      <guid>https://dev.to/lksvn/a-rename-can-be-a-migration-in-a-local-first-markdown-system-2bld</guid>
      <description>&lt;p&gt;It is easy to assume that an application backed by Markdown files avoids the&lt;br&gt;
hard parts of data migration. There is no database schema to alter, no foreign&lt;br&gt;
key constraint to update, and no migration framework to run.&lt;/p&gt;

&lt;p&gt;That assumption stops working as soon as filenames, metadata, links, and assets&lt;br&gt;
begin carrying identity.&lt;/p&gt;

&lt;p&gt;I encountered this while improving a Vue-based manager for a local card&lt;br&gt;
collection. Each saved card is a Markdown note that can link to other notes and&lt;br&gt;
reference a local image. Older records predated the current identity rules, so&lt;br&gt;
an audit found notes with missing IDs, duplicate IDs, and filenames that no&lt;br&gt;
longer matched their saved card printing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why changing one property was not enough
&lt;/h2&gt;

&lt;p&gt;The first repair step looked small: select the correct printing and save its&lt;br&gt;
stable identifier. But that identifier affected several parts of the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the expected Markdown filename&lt;/li&gt;
&lt;li&gt;the image filename and &lt;code&gt;Cover&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;links written in other Markdown notes&lt;/li&gt;
&lt;li&gt;duplicate identity checks&lt;/li&gt;
&lt;li&gt;filename collision checks&lt;/li&gt;
&lt;li&gt;cleanup of the previous image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Updating only the front matter would leave the collection internally&lt;br&gt;
inconsistent. Renaming only the note could break Obsidian links. Deleting the&lt;br&gt;
old image could break another note if the asset was shared.&lt;/p&gt;

&lt;p&gt;The operation was not a field edit. It was a migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treating the filesystem as a data model
&lt;/h2&gt;

&lt;p&gt;The repair flow became an explicit sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the requested identity.&lt;/li&gt;
&lt;li&gt;Reject duplicate identities and target filename collisions.&lt;/li&gt;
&lt;li&gt;Rename the note to its stable filename.&lt;/li&gt;
&lt;li&gt;Rewrite Markdown and wiki links that reference the previous name.&lt;/li&gt;
&lt;li&gt;Update the card image and &lt;code&gt;Cover&lt;/code&gt; metadata.&lt;/li&gt;
&lt;li&gt;Remove obsolete assets only after confirming they are unused.&lt;/li&gt;
&lt;li&gt;Run the collection audit again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The ordering matters. A failed rename should not leave references pointing to a&lt;br&gt;
file that never moved. Cleanup should happen after the new state is valid, not&lt;br&gt;
before it. Collision checks should fail safely instead of guessing which file&lt;br&gt;
is authoritative.&lt;/p&gt;

&lt;p&gt;This is similar to a database migration even though the storage mechanism is&lt;br&gt;
different. The invariants still need to be explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one stable identity belongs to one saved card&lt;/li&gt;
&lt;li&gt;a stable filename must not overwrite another note&lt;/li&gt;
&lt;li&gt;references must continue resolving after a rename&lt;/li&gt;
&lt;li&gt;a shared asset must not be removed while another note still uses it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verification beyond the happy path
&lt;/h2&gt;

&lt;p&gt;The backend route tests cover validation, collision handling, link rewriting,&lt;br&gt;
and repair behavior. A complete browser lifecycle test exercises the feature&lt;br&gt;
through the real application flow.&lt;/p&gt;

&lt;p&gt;After the repair work was completed, the collection was normalized and the&lt;br&gt;
audit reported no remaining identity or filename issues.&lt;/p&gt;

&lt;p&gt;The most important verification was not that a card could be repaired once. It&lt;br&gt;
was that the operation failed safely when the target identity or filename was&lt;br&gt;
already in use, and that rerunning the audit produced a stable result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Local-first storage changes the infrastructure, not the need for integrity.&lt;/p&gt;

&lt;p&gt;In a relational database, relationships are visible in tables, keys, and&lt;br&gt;
constraints. In a filesystem-backed application, they may be distributed&lt;br&gt;
across filenames, front matter, links, configuration, and shared assets. That&lt;br&gt;
makes them easier to overlook, not less important.&lt;/p&gt;

&lt;p&gt;The practical rule I am keeping is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If changing a value can invalidate references elsewhere, the operation should&lt;br&gt;
be designed and verified as a migration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Plain files are a powerful source of truth, but they still deserve explicit&lt;br&gt;
identity rules, safe failure modes, and repeatable audits.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Local-First Personal Finance System in Obsidian</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Tue, 04 Aug 2026 12:45:13 +0000</pubDate>
      <link>https://dev.to/lksvn/building-a-local-first-personal-finance-system-in-obsidian-3knb</link>
      <guid>https://dev.to/lksvn/building-a-local-first-personal-finance-system-in-obsidian-3knb</guid>
      <description>&lt;p&gt;For several years, I tracked personal finances in monthly spreadsheets. The&lt;br&gt;
system worked, but the format evolved over time, recurring expenses were easy to&lt;br&gt;
miss, and reviewing accounts payable and receivable required navigating many&lt;br&gt;
disconnected files.&lt;/p&gt;

&lt;p&gt;When I started consolidating the data, I did not want to create another hosted&lt;br&gt;
application or move sensitive financial information into an external service.&lt;br&gt;
I wanted the data to remain local, portable, and understandable without a&lt;br&gt;
special runtime.&lt;/p&gt;

&lt;p&gt;Obsidian provided a useful middle ground: Markdown files for durable storage,&lt;br&gt;
links for relationships, Bases for structured views, and QuickAdd for lightweight&lt;br&gt;
automation.&lt;/p&gt;
&lt;h2&gt;
  
  
  One transaction, one Markdown file
&lt;/h2&gt;

&lt;p&gt;The central design decision is simple: every financial transaction is an&lt;br&gt;
individual Markdown note with YAML properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;person&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[[FINANCES/PEOPLE/Example&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;person|Example&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;person]]"&lt;/span&gt;
&lt;span class="na"&gt;movement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pay&lt;/span&gt;
&lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2030-01&lt;/span&gt;
&lt;span class="na"&gt;period_note&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[[FINANCES/PERIODS/2030-01|2030-01]]"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fictional bill&lt;/span&gt;
&lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100.00&lt;/span&gt;
&lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2030-01-10&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pending&lt;/span&gt;
&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[[FINANCES/CATEGORIES/Other|Other]]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more verbose than a spreadsheet row, but it has useful properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the data remains ordinary text&lt;/li&gt;
&lt;li&gt;every transaction can link to related notes&lt;/li&gt;
&lt;li&gt;Git can show meaningful changes&lt;/li&gt;
&lt;li&gt;searches and scripts do not depend on a proprietary database&lt;/li&gt;
&lt;li&gt;Obsidian can present the same files through multiple views&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source of truth is the transaction note. Dashboards and tables only query&lt;br&gt;
those notes, which avoids having to update the same financial entry in multiple&lt;br&gt;
places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relationships through links
&lt;/h2&gt;

&lt;p&gt;People, categories, periods, and recurrence definitions are also notes. A&lt;br&gt;
transaction links to them through properties instead of repeating loosely&lt;br&gt;
normalized text.&lt;/p&gt;

&lt;p&gt;This improves filtering and navigation while also making Obsidian's graph and&lt;br&gt;
backlinks useful. The graph is not the primary interface for financial analysis,&lt;br&gt;
but it provides a quick view of how records connect to people, periods, and&lt;br&gt;
categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboards with Bases
&lt;/h2&gt;

&lt;p&gt;Obsidian Bases turns the Markdown collection into database-like views without&lt;br&gt;
changing the storage format.&lt;/p&gt;

&lt;p&gt;The template includes views for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current month&lt;/li&gt;
&lt;li&gt;pending transactions&lt;/li&gt;
&lt;li&gt;accounts payable and receivable&lt;/li&gt;
&lt;li&gt;completed and cancelled records&lt;/li&gt;
&lt;li&gt;installments and recurring transactions&lt;/li&gt;
&lt;li&gt;expenses grouped by category&lt;/li&gt;
&lt;li&gt;monthly expenses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Movement and status formulas add visual markers so inflows, outflows, pending&lt;br&gt;
items, and completed items are easier to distinguish at a glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recurring transactions without duplicate sources of truth
&lt;/h2&gt;

&lt;p&gt;Recurring expenses initially created an awkward problem. Editing both a&lt;br&gt;
recurrence definition and an already generated monthly transaction meant the&lt;br&gt;
same information existed in two places.&lt;/p&gt;

&lt;p&gt;The revised model gives each file a clear responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a recurrence note describes the rule&lt;/li&gt;
&lt;li&gt;a generated transaction represents one specific month&lt;/li&gt;
&lt;li&gt;dashboards only read transaction notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fixed expenses use their configured expected amount. Variable expenses can use&lt;br&gt;
the latest matching transaction as the starting value for a new month. Charges&lt;br&gt;
already included in a credit card bill can be documented without generating an&lt;br&gt;
additional transaction.&lt;/p&gt;

&lt;p&gt;The monthly generator is idempotent: running it again for the same period does&lt;br&gt;
not create duplicate transactions. Installment plans also stop after their final&lt;br&gt;
configured period.&lt;/p&gt;

&lt;h2&gt;
  
  
  QuickAdd as the interaction layer
&lt;/h2&gt;

&lt;p&gt;Two QuickAdd actions provide the main workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create a new financial transaction&lt;/li&gt;
&lt;li&gt;generate the transactions for a new month&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scripts create the required period note and transaction file, while the&lt;br&gt;
user still edits ordinary Markdown afterward. QuickAdd is an interaction layer,&lt;br&gt;
not a separate database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy and portability
&lt;/h2&gt;

&lt;p&gt;The vault is synchronized between devices using an independent file-sync setup.&lt;br&gt;
The finance model itself does not require a cloud account or a finance-specific&lt;br&gt;
provider.&lt;/p&gt;

&lt;p&gt;For the public template, generated people, periods, transactions, Obsidian&lt;br&gt;
workspace state, and environment files are excluded through &lt;code&gt;.gitignore&lt;/code&gt;. The&lt;br&gt;
repository contains only the reusable structure, scripts, documentation, and&lt;br&gt;
fictional screenshots.&lt;/p&gt;

&lt;p&gt;Local-first does not eliminate the need for backups or device security, but it&lt;br&gt;
does keep ownership and portability explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;This project reinforced a few ideas that also apply outside personal finance:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A single source of truth matters even in small personal systems.&lt;/li&gt;
&lt;li&gt;Automation should remove repetitive input without hiding the underlying data.&lt;/li&gt;
&lt;li&gt;Plain files can support surprisingly rich workflows when metadata and links
are modeled deliberately.&lt;/li&gt;
&lt;li&gt;A useful internal tool does not need to become a hosted product.&lt;/li&gt;
&lt;li&gt;Publishing a sanitized template requires treating privacy as part of the
architecture, not as a final cleanup step.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting project is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lksvn/obsidian-finances-template" rel="noopener noreferrer"&gt;https://github.com/lksvn/obsidian-finances-template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It includes the empty vault structure, setup documentation, Bases definitions,&lt;br&gt;
QuickAdd scripts, recurrence rules, and fictional screenshots.&lt;/p&gt;

</description>
      <category>obsidian</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Every Layer Must Earn Its Place</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Sat, 01 Aug 2026 01:26:50 +0000</pubDate>
      <link>https://dev.to/lksvn/every-layer-must-earn-its-place-56pe</link>
      <guid>https://dev.to/lksvn/every-layer-must-earn-its-place-56pe</guid>
      <description>&lt;p&gt;While starting a practical transition into C# and ASP.NET Core, I found that a&lt;br&gt;
simple endpoint was becoming harder to understand as each concept introduced a&lt;br&gt;
new structural element: endpoint, service, repository, interface, DTO, and&lt;br&gt;
result type.&lt;/p&gt;

&lt;p&gt;Each tool can solve a real problem. The problem is adopting the complete stack&lt;br&gt;
before those problems exist.&lt;/p&gt;

&lt;p&gt;My working rule is now incremental:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with the smallest endpoint that expresses the behavior.&lt;/li&gt;
&lt;li&gt;Extract application behavior when HTTP and business rules begin changing
for different reasons.&lt;/li&gt;
&lt;li&gt;Add a persistence boundary when it protects the application from meaningful
storage concerns or supports valuable test isolation.&lt;/li&gt;
&lt;li&gt;Add interfaces when multiple implementations or a genuine boundary exists.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not anti-architecture. It is architecture proportional to the system.&lt;br&gt;
A modular monolith with clear responsibilities can be safer than a deeply&lt;br&gt;
layered or distributed design whose abstractions mostly forward calls.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>learning</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Done Is Finally Better Than Perfect</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Tue, 28 Jul 2026 20:51:39 +0000</pubDate>
      <link>https://dev.to/lksvn/done-is-finally-better-than-perfect-2ok6</link>
      <guid>https://dev.to/lksvn/done-is-finally-better-than-perfect-2ok6</guid>
      <description>&lt;p&gt;I finally shipped the first version of my freelance landing page.&lt;/p&gt;

&lt;p&gt;The funny part?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I spent weeks thinking I had a design problem.&lt;/li&gt;
&lt;li&gt;I didn't.&lt;/li&gt;
&lt;li&gt;I had a content problem.&lt;/li&gt;
&lt;li&gt;The layout is good enough.&lt;/li&gt;
&lt;li&gt;The copy is good enough.&lt;/li&gt;
&lt;li&gt;The CSS is good enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What the page really needs now is more real projects.&lt;/p&gt;

&lt;p&gt;Instead of redesigning it again, I'm going to spend my time replacing concept work with actual client work as it comes in.&lt;/p&gt;

&lt;p&gt;Sometimes the next version isn't another refactor.&lt;/p&gt;

&lt;p&gt;It's simply experience.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://lksvn.com.br/freelance/" rel="noopener noreferrer"&gt;https://lksvn.com.br/freelance/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>freelance</category>
      <category>portfolio</category>
    </item>
    <item>
      <title>Omitted Is Not Empty: Modeling Nullable Partial Updates</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Mon, 27 Jul 2026 19:19:44 +0000</pubDate>
      <link>https://dev.to/lksvn/omitted-is-not-empty-modeling-nullable-partial-updates-9po</link>
      <guid>https://dev.to/lksvn/omitted-is-not-empty-modeling-nullable-partial-updates-9po</guid>
      <description>&lt;p&gt;While adding Job Opportunity editing to my Candidate Tracker, one optional description produced three different behaviors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;omit the property to preserve the stored value;&lt;/li&gt;
&lt;li&gt;provide a string to replace it;&lt;/li&gt;
&lt;li&gt;provide &lt;code&gt;null&lt;/code&gt; to clear it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first version mixed &lt;code&gt;undefined&lt;/code&gt; and &lt;code&gt;null&lt;/code&gt; across the domain, Prisma adapter, application service, and form action. That made clearing a value ambiguous.&lt;/p&gt;

&lt;p&gt;The final convention uses &lt;code&gt;string | null&lt;/code&gt; for persisted domain entities and optional properties for partial update commands. PostgreSQL &lt;code&gt;NULL&lt;/code&gt;, Prisma &lt;code&gt;null&lt;/code&gt;, and the domain now use the same absence representation. At the update boundary, omission still means “do not change.”&lt;/p&gt;

&lt;p&gt;The broader lesson is that update types describe commands and state transitions. They should distinguish every behavior the application needs instead of merely reusing an entity shape without considering omission.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>Tests Should Buy Confidence, Not Become the Feature</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Fri, 24 Jul 2026 20:28:35 +0000</pubDate>
      <link>https://dev.to/lksvn/tests-should-buy-confidence-not-become-the-feature-46ma</link>
      <guid>https://dev.to/lksvn/tests-should-buy-confidence-not-become-the-feature-46ma</guid>
      <description>&lt;p&gt;While implementing the Job Opportunity creation workflow in my Candidate Tracker, I reached an uncomfortable point: the application-service tests felt harder to understand than the feature itself.&lt;/p&gt;

&lt;p&gt;The production workflow was small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate and normalize the submitted input.&lt;/li&gt;
&lt;li&gt;Confirm that the selected Company exists.&lt;/li&gt;
&lt;li&gt;Create the Job Opportunity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tests introduced much more vocabulary and setup: fixtures, fakes, spies, mocks, controlled failures, and assertions that certain repository methods were never called.&lt;/p&gt;

&lt;p&gt;That complexity is not automatically a problem. A test must construct the world in which a behavior runs. However, it made me reconsider what each test was actually buying.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tests I kept
&lt;/h2&gt;

&lt;p&gt;I kept three application-service cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;valid input is normalized and created;&lt;/li&gt;
&lt;li&gt;invalid input stops before persistence;&lt;/li&gt;
&lt;li&gt;a missing Company does not create an orphan opportunity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cases protect business decisions. If any of them breaks, the application can accept invalid data or violate an important relationship.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tests I did not add
&lt;/h2&gt;

&lt;p&gt;The service also forwards failures returned by repositories. I could mock every repository method and test every forwarding branch, but those tests would mostly prove that:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;&lt;br&gt;
 {data-source-line="157"}&lt;/p&gt;

&lt;p&gt;returns the same result it received.&lt;/p&gt;

&lt;p&gt;That behavior is already constrained by TypeScript, while real PostgreSQL integration tests verify persistence behavior. The complete browser workflow was also checked manually.&lt;/p&gt;

&lt;p&gt;Adding more mocks would increase the test count, but not necessarily my confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the right testing level
&lt;/h2&gt;

&lt;p&gt;My current rule is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unit-test business rules and meaningful decisions;&lt;/li&gt;
&lt;li&gt;integration-test actual persistence behavior;&lt;/li&gt;
&lt;li&gt;verify the complete user workflow;&lt;/li&gt;
&lt;li&gt;add regression tests when real bugs reveal a missing case;&lt;/li&gt;
&lt;li&gt;do not optimize for test count or branch coverage alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not an argument against testing. It is an argument for making every test justify its maintenance cost.&lt;/p&gt;

&lt;p&gt;Tests should reduce the fear of changing code. When the setup becomes harder to understand than the protected behavior, the testing level or scope may need simplification—not necessarily the feature.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>learning</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A Prisma Schema Change Has Three Lifecycles</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Wed, 22 Jul 2026 21:47:40 +0000</pubDate>
      <link>https://dev.to/lksvn/a-prisma-schema-change-has-three-lifecycles-3pcn</link>
      <guid>https://dev.to/lksvn/a-prisma-schema-change-has-three-lifecycles-3pcn</guid>
      <description>&lt;p&gt;One valid PostgreSQL row refused to appear on my Next.js page.&lt;/p&gt;

&lt;p&gt;The relationship was correct, the SQL join returned the row, and the repository compiled. At runtime, however, Prisma returned an undefined &lt;code&gt;jobOpportunity&lt;/code&gt; relation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stale layer was not the database
&lt;/h2&gt;

&lt;p&gt;I had changed the Prisma schema while the development server was running. That change involved three independent lifecycles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;prisma migrate&lt;/code&gt; updates the database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prisma generate&lt;/code&gt; updates the TypeScript client.&lt;/li&gt;
&lt;li&gt;Restarting Next.js replaces the stale generated client held by the running process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two steps were complete. The third was not.&lt;/p&gt;

&lt;p&gt;Coming from PHP and CodeIgniter, this is an important mental-model shift. A request-scoped PHP application normally loads changed code on the next request. A long-running Node.js process can keep generated code and development caches alive.&lt;/p&gt;

&lt;p&gt;Prisma provides valuable type safety, but it does not remove complexity—it relocates it.&lt;/p&gt;

&lt;p&gt;My new debugging rule is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application edit → trust hot reload.&lt;/li&gt;
&lt;li&gt;Prisma model change → migrate, generate, restart.&lt;/li&gt;
&lt;li&gt;Suspicious stale behavior → clear the framework cache and restart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before rewriting a correct query, identify which layer is stale.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>prisma</category>
      <category>nextjs</category>
      <category>learning</category>
    </item>
    <item>
      <title>Not Every Productive Day Looks Like Writing Code</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Wed, 22 Jul 2026 01:11:55 +0000</pubDate>
      <link>https://dev.to/lksvn/not-every-productive-day-looks-like-writing-code-91h</link>
      <guid>https://dev.to/lksvn/not-every-productive-day-looks-like-writing-code-91h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Sometimes the most valuable work doesn't produce a single commit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today was one of those days.&lt;/p&gt;

&lt;p&gt;I didn't implement a new feature in my TypeScript project.&lt;/p&gt;

&lt;p&gt;I didn't learn a new framework.&lt;/p&gt;

&lt;p&gt;I didn't even spend much time writing code.&lt;/p&gt;

&lt;p&gt;At first, that felt unproductive.&lt;/p&gt;

&lt;p&gt;But looking back, I realized I spent the day investing in something that will make future work easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Infrastructure Around Development
&lt;/h2&gt;

&lt;p&gt;The first thing I worked on was updating my &lt;code&gt;fresh-install&lt;/code&gt; script.&lt;/p&gt;

&lt;p&gt;Every time I format my machine or switch computers, I find myself repeating the same setup steps. Automating those tasks doesn't make my GitHub contribution graph greener, but it saves hours every time I need a fresh environment.&lt;/p&gt;

&lt;p&gt;Then I spent several hours reorganizing my personal knowledge base.&lt;/p&gt;

&lt;p&gt;I reviewed old notes, connected documentation from personal projects, organized topics by area, improved tagging, and turned scattered information into something I can actually navigate.&lt;/p&gt;

&lt;p&gt;It wasn't exciting.&lt;/p&gt;

&lt;p&gt;It wasn't glamorous.&lt;/p&gt;

&lt;p&gt;But it felt necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Optimize Applications...
&lt;/h2&gt;

&lt;p&gt;As developers, we spend a lot of time reducing friction for users.&lt;/p&gt;

&lt;p&gt;We optimize APIs.&lt;/p&gt;

&lt;p&gt;We improve application performance.&lt;/p&gt;

&lt;p&gt;We refactor code to make future changes easier.&lt;/p&gt;

&lt;p&gt;But we rarely apply the same mindset to ourselves.&lt;/p&gt;

&lt;p&gt;Why should finding a note from six months ago take longer than searching production logs?&lt;/p&gt;

&lt;p&gt;Why should I rediscover the same solution every few months?&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowledge Compounds
&lt;/h2&gt;

&lt;p&gt;One thing I've been realizing while working on my &lt;strong&gt;Candidate Tracker&lt;/strong&gt; project is that the code is only part of the learning process.&lt;/p&gt;

&lt;p&gt;The architecture decisions.&lt;/p&gt;

&lt;p&gt;The trade-offs.&lt;/p&gt;

&lt;p&gt;The mistakes.&lt;/p&gt;

&lt;p&gt;The reasons why I chose one approach instead of another.&lt;/p&gt;

&lt;p&gt;Those are often more valuable than the final implementation.&lt;/p&gt;

&lt;p&gt;If I don't capture them somewhere, I'll eventually lose them.&lt;/p&gt;

&lt;p&gt;A personal knowledge base isn't just documentation.&lt;/p&gt;

&lt;p&gt;It's an extension of memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invisible Progress
&lt;/h2&gt;

&lt;p&gt;It's easy to measure productivity by commits.&lt;/p&gt;

&lt;p&gt;It's much harder to measure things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better documentation&lt;/li&gt;
&lt;li&gt;Faster onboarding to your own projects&lt;/li&gt;
&lt;li&gt;Easier environment setup&lt;/li&gt;
&lt;li&gt;Less context switching&lt;/li&gt;
&lt;li&gt;Less time searching for information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These improvements don't show up on GitHub.&lt;/p&gt;

&lt;p&gt;But they compound over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Today reminded me that software engineering isn't only about writing code.&lt;/p&gt;

&lt;p&gt;Sometimes the best investment you can make is reducing the friction of tomorrow's work.&lt;/p&gt;

&lt;p&gt;The results may not be visible immediately.&lt;/p&gt;

&lt;p&gt;But future you will definitely notice.&lt;/p&gt;




&lt;p&gt;I'd love to know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's one "behind the scenes" improvement that has made the biggest difference in your daily workflow?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>career</category>
      <category>productivity</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Architecture starts paying rent when a user workflow reaches it</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Mon, 20 Jul 2026 20:47:42 +0000</pubDate>
      <link>https://dev.to/lksvn/architecture-starts-paying-rent-when-a-user-workflow-reaches-it-5e81</link>
      <guid>https://dev.to/lksvn/architecture-starts-paying-rent-when-a-user-workflow-reaches-it-5e81</guid>
      <description>&lt;p&gt;My TypeScript Candidate Tracker passed 100 tests before it had a page I could actually use.&lt;/p&gt;

&lt;p&gt;The repository contracts, Result types, validation services, Prisma adapter, and PostgreSQL integration all had value—but the project still felt like it was walking in place.&lt;/p&gt;

&lt;p&gt;Today I changed direction and connected one complete Company workflow to Next.js:&lt;/p&gt;

&lt;p&gt;FormData from the browser&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runtime shape validation&lt;/li&gt;
&lt;li&gt;business validation&lt;/li&gt;
&lt;li&gt;application service&lt;/li&gt;
&lt;li&gt;repository&lt;/li&gt;
&lt;li&gt;Prisma&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;refreshed Server Component&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same application now lists, creates, displays, and edits real persisted Companies.&lt;/p&gt;

&lt;p&gt;Several earlier decisions finally became concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser validation improves UX but cannot protect the server&lt;/li&gt;
&lt;li&gt;successful database absence becomes an explicit not-found outcome&lt;/li&gt;
&lt;li&gt;Server Actions are real public input boundaries&lt;/li&gt;
&lt;li&gt;a shared Prisma client matters under development hot reload&lt;/li&gt;
&lt;li&gt;database-generated UUIDs work beyond Prisma Client, including Studio and raw SQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson was not that architecture is useless. It was that architecture needs a consumer.&lt;/p&gt;

&lt;p&gt;Once a real user workflow crossed every layer, the abstractions stopped feeling theoretical and started explaining their cost.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>learning</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Stronger application-layer types remove impossible states</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Sat, 18 Jul 2026 17:00:00 +0000</pubDate>
      <link>https://dev.to/lksvn/stronger-application-layer-types-remove-impossible-states-53m4</link>
      <guid>https://dev.to/lksvn/stronger-application-layer-types-remove-impossible-states-53m4</guid>
      <description>&lt;p&gt;Today I completed a small Company write vertical slice in TypeScript, from business validation to PostgreSQL persistence.&lt;/p&gt;

&lt;p&gt;One detail clarified why application services are more than repository wrappers.&lt;/p&gt;

&lt;p&gt;The repository update returns &lt;code&gt;Company | undefined&lt;/code&gt;. That makes sense at the data layer: the query can succeed while the requested row does not exist.&lt;/p&gt;

&lt;p&gt;But the application service translates &lt;code&gt;undefined&lt;/code&gt; into an explicit &lt;code&gt;not-found&lt;/code&gt; result.&lt;/p&gt;

&lt;p&gt;After that translation, keeping &lt;code&gt;Company | undefined&lt;/code&gt; in the successful return type would force every caller to handle a state the service has made impossible.&lt;/p&gt;

&lt;p&gt;So the application contract returns either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;success with a Company&lt;/li&gt;
&lt;li&gt;validation failure&lt;/li&gt;
&lt;li&gt;not-found failure&lt;/li&gt;
&lt;li&gt;repository failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The type now describes the use-case guarantee instead of leaking the repository's lower-level uncertainty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical lesson:&lt;/strong&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;good TypeScript types do more than prevent syntax mistakes. They help make invalid or impossible application states unrepresentable.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A passing test can still prove nothing</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Fri, 17 Jul 2026 01:42:52 +0000</pubDate>
      <link>https://dev.to/lksvn/a-passing-test-can-still-prove-nothing-2p16</link>
      <guid>https://dev.to/lksvn/a-passing-test-can-still-prove-nothing-2p16</guid>
      <description>&lt;p&gt;Today I had a &lt;strong&gt;PostgreSQL&lt;/strong&gt; integration test passing for the wrong reason.&lt;/p&gt;

&lt;p&gt;The test was supposed to prove that updating a missing Company returned a successful "not found" result.&lt;/p&gt;

&lt;p&gt;I passed an id like: &lt;code&gt;company-missing&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But the database column was a &lt;strong&gt;UUID&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; rejected the malformed value before &lt;strong&gt;Prisma&lt;/strong&gt; could determine whether the Company existed. The repository returned a failure instead of the expected missing-record outcome.&lt;/p&gt;

&lt;p&gt;The test still passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Its assertion was inside a conditional success branch. Because the result was a failure, the branch never ran. The test completed with no failing assertion.&lt;/p&gt;

&lt;p&gt;The correction was small but important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate a valid UUID that was never inserted&lt;/li&gt;
&lt;li&gt;assert the complete Result object directly&lt;/li&gt;
&lt;li&gt;expect success with undefined data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made the test exercise Prisma's real missing-record behavior instead of an invalid-input error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A green test is not automatically evidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check that the intended branch was reached and that the assertion could actually fail.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Strong TypeScript contracts turn refactors into checklists</title>
      <dc:creator>LkSvn</dc:creator>
      <pubDate>Wed, 15 Jul 2026 15:10:00 +0000</pubDate>
      <link>https://dev.to/lksvn/strong-typescript-contracts-turn-refactors-into-checklists-40h4</link>
      <guid>https://dev.to/lksvn/strong-typescript-contracts-turn-refactors-into-checklists-40h4</guid>
      <description>&lt;p&gt;Today a TypeScript refactor produced &lt;strong&gt;15 compiler errors&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It looked worse than it was.  &lt;/p&gt;

&lt;p&gt;My Candidate Tracker used a type called &lt;code&gt;AsyncResult&amp;lt;T&amp;gt;&lt;/code&gt; with errors represented only as strings.  &lt;/p&gt;

&lt;p&gt;While adding a real PostgreSQL repository, two design problems became clear:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the result itself was not asynchronous; the surrounding Promise was &lt;/li&gt;
&lt;li&gt;an error string discarded its category and original cause  I changed the contract to &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; and introduced a typed &lt;code&gt;RepositoryError&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The compiler immediately identified every repository, dashboard function, test, and callback that still depended on the old contract.  &lt;/p&gt;

&lt;p&gt;Most of the 15 errors were not separate problems. They were downstream symptoms of a few outdated type definitions.  &lt;/p&gt;

&lt;p&gt;Once those source contracts were corrected, the remaining errors disappeared and all tests passed again.  &lt;/p&gt;

&lt;p&gt;The lesson was practical:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Strong types do more than prevent mistakes while writing code. They make architectural changes searchable, explicit, and finite.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>typescript</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
