<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://gregcurl.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://gregcurl.dev/" rel="alternate" type="text/html" /><updated>2026-06-01T19:18:32+00:00</updated><id>https://gregcurl.dev/feed.xml</id><title type="html">Greg Curl</title><subtitle>Data Engineering, Analytics, and Writing</subtitle><entry><title type="html"></title><link href="https://gregcurl.dev/2026-06-01-tightening-assettrack-before-the-next-workflow-change/" rel="alternate" type="text/html" title="" /><published>2026-06-01T19:18:32+00:00</published><updated>2026-06-01T19:18:32+00:00</updated><id>https://gregcurl.dev/2026-06-01-tightening-assettrack-before-the-next-workflow-change</id><content type="html" xml:base="https://gregcurl.dev/2026-06-01-tightening-assettrack-before-the-next-workflow-change/"><![CDATA[<p>TL;DR: Today was not about adding big new features. It was about closing open loops, documenting guardrails, and making sure future workflow changes do not weaken AssetTrack’s event-sourced custody model.</p>

<p>Project page: AssetTrack</p>

<h2 id="context">Context</h2>

<p>AssetTrack is built around one simple rule:</p>

<p>The event log is the truth.</p>

<p>That means every workflow change has to be careful. A cleaner button, a better email action, or a smoother validation screen cannot quietly change custody truth, receipt truth, audit history, or persistence behavior.</p>

<p>Today’s work stayed focused on that discipline.</p>

<p>The recent receipt email work had already made the admin resend path safer and cleaner. From there, the next question was not “what can we build next?” It was “what do we need to close, document, and understand before the next behavior change?”</p>

<p>That is slower in the short term, but safer for the system.</p>

<h2 id="what-changed">What changed</h2>

<p>We worked through several Issue 27 cleanup and recon items.</p>

<p>First, we confirmed that the holder follow-up email action was already implemented on <code class="language-plaintext highlighter-rouge">main</code> by earlier work. No new code was needed. The existing behavior already supports a manual holder follow-up email from the holder detail page, using the local SMTP configuration, without writing to the database or changing custody or receipt truth.</p>

<p>That issue was closed as already satisfied.</p>

<p>Next, we added a pre-event validation checklist.</p>

<p>That checklist documents what AssetTrack must confirm before any event-producing workflow appends custody history. It covers authentication, authorization, workflow intent, required context, asset eligibility, queue validity, preview confirmation, append-only commit boundaries, SQLite persistence expectations, post-commit reconciliation, and receipt/email separation.</p>

<p>The point is simple:</p>

<p>Validation happens before the event is appended.</p>

<p>Once the event is appended, history is not edited to make the workflow feel cleaner later.</p>

<p>Then we completed recon on multi-location holder support.</p>

<p>The finding was important. Holders are currently global custody actors, not location-scoped records. A holder can operate across approved locations without needing a schema change. Issue location is transaction context. Receipts snapshot holder identity and location separately. That means no runtime change is needed right now for a holder working across multiple locations.</p>

<p>The recon also identified two possible future paths, but only if operators need them later:</p>

<ol>
  <li>Read-only holder location filtering semantics.</li>
  <li>Persistent holder-to-location membership with schema and migration planning.</li>
</ol>

<p>Finally, we completed recon on validation flow and redundancy.</p>

<p>The current Issue and Return workflows still preserve the correct seam:</p>

<p>entry page → prerequisite selection → scan queue → preview → commit</p>

<p>That seam matters because preview is the operator’s main checkpoint before custody events are appended. Commit handlers and transaction helpers also revalidate state before writing events. That duplication is intentional. It protects custody truth.</p>

<p>The recon found a few presentation-only simplification candidates, including repeated guidance and blocked-state summaries, but it also clearly marked the checks that must not be removed.</p>

<h2 id="what-i-learned">What I learned</h2>

<p>A system like AssetTrack does not get safer just because it has more code.</p>

<p>It gets safer when the rules are written down, checked, and respected.</p>

<p>The pre-event checklist gives future issues a shared reference point. Instead of re-arguing every workflow decision from scratch, we now have a plain checklist that says what must be true before AssetTrack writes an event.</p>

<p>The multi-location holder recon also helped prevent premature schema work. It would be easy to assume holders need location membership records. But the current model already supports the real operational case: a global custody actor participating in transactions at different locations.</p>

<p>That matters because schema changes are expensive. In AssetTrack, they are not just database changes. They affect event interpretation, receipts, custody reports, workflows, and operator trust.</p>

<p>The validation recon reinforced another lesson:</p>

<p>Some repetition is bad interface design. Some repetition is system protection.</p>

<p>The job is knowing which is which.</p>

<p>Repeated helper text may be simplified later. Commit-time validation should stay.</p>

<h2 id="next">Next</h2>

<p>The next useful work should stay small and disciplined.</p>

<p>Good candidates include:</p>

<ul>
  <li>presentation-only cleanup from the validation recon</li>
  <li>issue prerequisite guidance compression</li>
  <li>return preview blocked-state summary cleanup</li>
  <li>case structure and capacity recon</li>
  <li>asset search improvements</li>
</ul>

<p>Before any behavior-changing issue, the new pre-event validation checklist should be used as a guardrail.</p>

<p>The goal remains the same:</p>

<p>Keep AssetTrack simple for the operator without making the custody model weaker behind the scenes.</p>]]></content><author><name></name></author></entry><entry><title type="html">2026-05-12 — 🚜 AssetTrack: Operational calm and UI cohesion</title><link href="https://gregcurl.dev/projects/assettrack/operational-calm-and-ui-cohesion/" rel="alternate" type="text/html" title="2026-05-12 — 🚜 AssetTrack: Operational calm and UI cohesion" /><published>2026-05-12T00:00:00+00:00</published><updated>2026-05-12T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/assettrack/operational-calm-and-ui-cohesion</id><content type="html" xml:base="https://gregcurl.dev/projects/assettrack/operational-calm-and-ui-cohesion/"><![CDATA[<p>TL;DR: AssetTrack shifted from “dashboard-style admin app” toward calmer operational software by reducing duplicated actions, simplifying navigation, standardizing interaction patterns, and tightening workflow cohesion.</p>

<p>Project page: <a href="/projects/assettrack/">AssetTrack</a>.</p>

<h2 id="context">Context</h2>

<p>This phase of AssetTrack was not about adding features.</p>

<p>It was about reducing friction.</p>

<p>Over time, the system had accumulated:</p>

<ul>
  <li>duplicate navigation paths</li>
  <li>helper-text overload</li>
  <li>inconsistent return links</li>
  <li>equal-weight actions competing for attention</li>
  <li>admin pages that felt more like scaffolding than operational software</li>
</ul>

<p>Nothing was technically broken.</p>

<p>But the system was becoming mentally noisy.</p>

<p>That matters in a field-oriented operational application.</p>

<p>Operators should not feel like they are navigating a SaaS dashboard.</p>

<p>They should feel like they are operating a focused tool.</p>

<h2 id="what-changed">What changed</h2>

<p>Several connected UI refinement issues landed during this phase.</p>

<p>Highlights included:</p>

<ul>
  <li>consolidating duplicate report/dashboard actions</li>
  <li>reducing persistent navigation clutter</li>
  <li>introducing clearer action hierarchy</li>
  <li>standardizing return navigation patterns</li>
  <li>reducing visual competition between metadata and operational actions</li>
  <li>improving spacing and containment rhythm</li>
  <li>normalizing admin/workflow interaction patterns</li>
</ul>

<p>One interesting correction happened late in the cycle.</p>

<p>During consistency cleanup, the UI standardized on:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Stage Assets
</code></pre></div></div>

<p>But smoke testing showed that operators naturally responded better to:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Add Assets
</code></pre></div></div>

<p>That small wording difference mattered more than expected.</p>

<p>“Stage” reflected the internal workflow architecture.</p>

<p>“Add Assets” reflected the operator’s actual mental model.</p>

<p>The system now consistently uses:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Add Assets
→ Preview Queue
→ Commit
</code></pre></div></div>

<p>which reads much more naturally during operation.</p>

<h2 id="what-i-learned">What I learned</h2>

<p>There is a major difference between:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>functional UI
</code></pre></div></div>

<p>and:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>operational cognition
</code></pre></div></div>

<p>A system can technically work while still exhausting the operator.</p>

<p>As AssetTrack matured, the problems stopped being backend problems.</p>

<p>The problems became:</p>

<ul>
  <li>attention management</li>
  <li>workflow confidence</li>
  <li>interaction consistency</li>
  <li>visual hierarchy</li>
  <li>terminology precision</li>
</ul>

<p>The most surprising lesson was that reducing noise exposed deeper cohesion problems.</p>

<p>Once the clutter disappeared, inconsistency became visible immediately.</p>

<p>That was actually a good sign.</p>

<p>It meant the system was finally calm enough for polish problems to surface.</p>

<h2 id="next">Next</h2>

<p>Next work will likely focus on:</p>

<ul>
  <li>progressive disclosure for secondary operational details</li>
  <li>workflow surface compression</li>
  <li>continued reduction of “dashboard” behavior</li>
  <li>operational consistency across remaining admin and demo surfaces</li>
</ul>

<p>The goal is increasingly clear:</p>

<p>AssetTrack should feel less like a web application and more like a dedicated operational appliance.</p>]]></content><author><name></name></author><category term="projects" /><category term="assettrack" /><category term="assettrack" /><category term="ux" /><category term="operations" /><category term="ui" /><category term="cognition" /><summary type="html"><![CDATA[TL;DR: AssetTrack shifted from “dashboard-style admin app” toward calmer operational software by reducing duplicated actions, simplifying navigation, standardizing interaction patterns, and tightening workflow cohesion.]]></summary></entry><entry><title type="html">2026-05-07 — 🚜 AssetTrack: Theme and navigation cleanup</title><link href="https://gregcurl.dev/projects/assettrack/theme-and-navigation-cleanup/" rel="alternate" type="text/html" title="2026-05-07 — 🚜 AssetTrack: Theme and navigation cleanup" /><published>2026-05-07T00:00:00+00:00</published><updated>2026-05-07T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/assettrack/theme-and-navigation-cleanup</id><content type="html" xml:base="https://gregcurl.dev/projects/assettrack/theme-and-navigation-cleanup/"><![CDATA[<p>TL;DR: Today was a cleanup and cognition day. I tightened workflow navigation, standardized layout behavior, fixed a subtle accessibility issue in the theme toggle, and reduced navbar noise with an icon-only dark mode control.</p>

<p>Project page: <a href="/projects/assettrack/">AssetTrack</a>.</p>

<h2 id="context">Context</h2>

<p>AssetTrack is at the stage where the major workflows exist, but the operator experience is starting to matter more than raw feature count.</p>

<p>The system already works.</p>

<p>Now the question becomes:</p>

<blockquote>
  <p>Does the system <em>feel</em> understandable under pressure?</p>
</blockquote>

<p>That led into a chain of navigation and presentation cleanup issues focused on reducing cognitive load for operators.</p>

<p>The biggest realization today was that navigation has layers:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>global navigation
local workflow navigation
workflow actions
</code></pre></div></div>

<p>When those layers visually compete, the operator has to stop and think.</p>

<p>That is exactly what we do not want in a field inventory system.</p>

<h2 id="what-changed">What changed</h2>

<p>Completed UX/navigation cleanup chain:</p>

<ul>
  <li>Issue 27-39 — strengthened preview affordance</li>
  <li>Issue 27-40 — clarified <code class="language-plaintext highlighter-rouge">/add-assets</code> action hierarchy</li>
  <li>Issue 27-41 — simplified alternate workflow navigation</li>
  <li>Issue 27-43 — compacted shared top-nav chips</li>
  <li>Issue 27-44 — standardized workflow-local back links</li>
  <li>Issue 27-46 — aligned workflow/admin container widths</li>
  <li>Issue 27-47 — fixed server-rendered theme toggle pressed state</li>
  <li>Issue 27-49 — simplified theme toggle to icon-only</li>
</ul>

<p>The most interesting fix today was probably not visual at all.</p>

<p>The theme toggle had a subtle accessibility mismatch where the server-rendered HTML did not initially include the correct <code class="language-plaintext highlighter-rouge">aria-pressed</code> state. JavaScript eventually corrected it, but there was a brief mismatch between what the page showed and what accessibility tools understood.</p>

<p>The fix itself was tiny:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>one missing attribute
</code></pre></div></div>

<p>But those are the kinds of details that quietly erode trust over time if left unresolved.</p>

<p>I also simplified the dark mode toggle into a compact icon-only control. Removing words from the navbar sounds small, but it noticeably reduced visual noise.</p>

<h2 id="what-i-learned">What I learned</h2>

<p>I discovered an important distinction today:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>actual layout inconsistency
vs
perceived layout density
</code></pre></div></div>

<p>Originally I thought several admin pages had width problems.</p>

<p>After recon and testing, the real issue was only one outlier route using a narrower container.</p>

<p>Everything else that still <em>felt</em> different came from:</p>

<ul>
  <li>table density</li>
  <li>spacing rhythm</li>
  <li>control grouping</li>
  <li>visual composition</li>
</ul>

<p>That matters because it prevents over-fixing the wrong problem.</p>

<p>Another lesson:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tiny UI inconsistencies compound
</code></pre></div></div>

<p>One slightly different nav row.
One awkward button hierarchy.
One oversized text label.</p>

<p>Individually they seem harmless.</p>

<p>Together they create friction.</p>

<h2 id="next">Next</h2>

<p>Next up is likely continued workflow cognition cleanup:</p>

<ul>
  <li>separating mixed nav/action rows more aggressively</li>
  <li>refining dense admin surfaces</li>
  <li>continuing to reduce unnecessary wording</li>
  <li>preserving accessibility while simplifying presentation</li>
</ul>

<p>The system is starting to feel calmer.</p>

<p>That is the goal.</p>]]></content><author><name></name></author><category term="projects" /><category term="assettrack" /><category term="assettrack" /><category term="ux" /><category term="navigation" /><category term="accessibility" /><category term="css" /><category term="workflow" /><summary type="html"><![CDATA[TL;DR: Today was a cleanup and cognition day. I tightened workflow navigation, standardized layout behavior, fixed a subtle accessibility issue in the theme toggle, and reduced navbar noise with an icon-only dark mode control.]]></summary></entry><entry><title type="html">2026-04-26 — ⏱️ Settled on the Field: Going Live</title><link href="https://gregcurl.dev/projects/settled-field-platform/settled-on-the-field-going-live/" rel="alternate" type="text/html" title="2026-04-26 — ⏱️ Settled on the Field: Going Live" /><published>2026-04-26T00:00:00+00:00</published><updated>2026-04-26T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/settled-field-platform/settled-on-the-field-going-live</id><content type="html" xml:base="https://gregcurl.dev/projects/settled-field-platform/settled-on-the-field-going-live/"><![CDATA[<p>TL;DR: Settled on the Field is live at <a href="https://settledonthefield.com">settledonthefield.com</a>. The project moved from local build to public-facing site.</p>

<p>Project page: <a href="/projects/settled-field-platform/">Settled Field Platform</a>.</p>

<hr />

<h2 id="context">Context</h2>

<p>Today was about getting the site out of the local/dev lane and into the real world.</p>

<p>The goal was not perfection.</p>

<p>The goal was:</p>

<blockquote>
  <p>Make the site credible, clean, and available before Bill’s meeting.</p>
</blockquote>

<p>That meant focusing on flow, trust, and deployment instead of adding more features.</p>

<hr />

<h2 id="what-changed">What changed</h2>

<h3 id="the-site-is-live">The site is live</h3>

<p>Settled on the Field is now available here:</p>

<p><a href="https://settledonthefield.com">https://settledonthefield.com</a></p>

<p>The domain is managed through Bluehost, while the app is deployed through Vercel.</p>

<p>The key production setup became:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Bluehost DNS → Vercel → Settled Field Platform
</code></pre></div></div>

<hr />

<h3 id="the-public-experience-is-presentable">The public experience is presentable</h3>

<p>The site now has:</p>

<ul>
  <li>a clear landing page</li>
  <li>Summit path</li>
  <li>registration interest path</li>
  <li>clean footer</li>
  <li>truthful placeholder-safe contact language</li>
  <li>real domain access</li>
</ul>

<p>This moved the project from:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pages in progress
</code></pre></div></div>

<p>to:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a usable public experience
</code></pre></div></div>

<hr />

<h3 id="footer-attribution-was-cleaned-up">Footer attribution was cleaned up</h3>

<p>The CurlTech attribution went through a few iterations.</p>

<p>The final version uses:</p>

<ul>
  <li>a subtle sentence</li>
  <li>an inline SVG logo</li>
  <li>a link to <a href="https://gregcurl.dev">gregcurl.dev</a></li>
</ul>

<p>This kept the credit present without letting it compete with the site.</p>

<hr />

<h3 id="dns-was-the-real-boss-fight">DNS was the real boss fight</h3>

<p>The code was not the hard part today.</p>

<p>The tricky part was DNS:</p>

<ul>
  <li>root domain needed the correct Vercel A record</li>
  <li><code class="language-plaintext highlighter-rouge">www</code> needed the correct CNAME</li>
  <li>conflicting Bluehost records had to be removed</li>
  <li>Vercel needed time to validate the configuration</li>
</ul>

<p>The lesson:</p>

<blockquote>
  <p>If the site works but Vercel says invalid, check for duplicate or conflicting DNS records.</p>
</blockquote>

<hr />

<h2 id="what-i-learned">What I learned</h2>

<h3 id="1-live-changes-force-clarity">1. Live changes force clarity</h3>

<p>Once a site is public, placeholders feel different.</p>

<p>Every fake email, broken link, or loud attribution suddenly matters more.</p>

<p>That pressure was useful.</p>

<p>It forced the site to become more honest.</p>

<hr />

<h3 id="2-svg-was-the-right-answer">2. SVG was the right answer</h3>

<p>The footer logo issue looked like a CSS problem at first.</p>

<p>It was really an asset problem.</p>

<p>The PNG had sizing/canvas issues, so the fix was to use a proper SVG and size it with <code class="language-plaintext highlighter-rouge">em</code>.</p>

<p>Lesson:</p>

<blockquote>
  <p>Fix the asset layer before fighting CSS too hard.</p>
</blockquote>

<hr />

<h3 id="3-good-enough-to-present-is-a-real-milestone">3. “Good enough to present” is a real milestone</h3>

<p>This version is not the final product.</p>

<p>But it is good enough for a real conversation.</p>

<p>That matters.</p>

<p>A live, credible site creates momentum in a way a local build never can.</p>

<hr />

<h2 id="next">Next</h2>

<p>After Bill’s meeting, the next work should focus on:</p>

<ul>
  <li>real speaker content</li>
  <li>real partner logos</li>
  <li>confirmed social links</li>
  <li>confirmed contact method</li>
  <li>stronger Summit credibility</li>
  <li>payment flow when the timing is right</li>
</ul>

<hr />

<h2 id="closing-thought">Closing thought</h2>

<p>Today the project crossed a line.</p>

<p>It is no longer just a build.</p>

<p>It is now a live system with a real address:</p>

<p><a href="https://settledonthefield.com">settledonthefield.com</a></p>

<p>That changes the conversation.</p>]]></content><author><name></name></author><category term="projects" /><category term="settled-field-platform" /><category term="nextjs" /><category term="vercel" /><category term="bluehost" /><category term="dns" /><category term="deployment" /><category term="mvp" /><summary type="html"><![CDATA[TL;DR: Settled on the Field is live at settledonthefield.com. The project moved from local build to public-facing site.]]></summary></entry><entry><title type="html">2026-04-25 — 🧭 Making the site feel real</title><link href="https://gregcurl.dev/projects/settled-field-platform/making-the-site-feel-real/" rel="alternate" type="text/html" title="2026-04-25 — 🧭 Making the site feel real" /><published>2026-04-25T00:00:00+00:00</published><updated>2026-04-25T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/settled-field-platform/making-the-site-feel-real</id><content type="html" xml:base="https://gregcurl.dev/projects/settled-field-platform/making-the-site-feel-real/"><![CDATA[<p>TL;DR: Today wasn’t about adding features. It was about making the site feel real enough that someone would actually click.</p>

<p>Project page: <a href="/projects/settled-field-platform/">Settled Field Platform</a>.</p>

<hr />

<h2 id="context">Context</h2>

<p>I’m working toward a meeting-ready version of the Settled Field Platform site.</p>

<p>The goal isn’t perfection.<br />
It’s credibility.</p>

<p>If someone lands on the page, they should:</p>
<ul>
  <li>understand what this is</li>
  <li>trust it</li>
  <li>know what to do next</li>
</ul>

<p>Before today, the site had structure, but it still felt like a template.<br />
Clean… but not convincing.</p>

<hr />

<h2 id="what-changed">What changed</h2>

<h3 id="1-the-hero-stopped-feeling-like-a-brochure">1. The hero stopped feeling like a brochure</h3>

<p>I shifted the landing section into more of a “poster” feel:</p>
<ul>
  <li>stronger visual presence</li>
  <li>clearer headline</li>
  <li>real CTA positioning</li>
</ul>

<p>It started to feel like an event instead of a webpage.</p>

<hr />

<h3 id="2-typography-became-intentional">2. Typography became intentional</h3>

<p>I introduced:</p>
<ul>
  <li>Playfair Display for headings</li>
  <li>Inter for body text</li>
</ul>

<p>Then centralized everything in <code class="language-plaintext highlighter-rouge">globals.css</code>.</p>

<p>This wasn’t just about fonts.<br />
It made the hierarchy obvious and removed visual noise.</p>

<hr />

<h3 id="3-ctas-became-actual-actions">3. CTAs became actual actions</h3>

<p>Big realization here:</p>

<p>The buttons looked styled, but they didn’t <em>feel clickable</em>.</p>

<p>The issue wasn’t color — it was separation.</p>

<p>Fix:</p>
<ul>
  <li>stronger fill</li>
  <li>real shadow (not glow)</li>
  <li>clear elevation off the background image</li>
</ul>

<p>Now the CTA sits <em>above</em> the hero instead of blending into it.</p>

<hr />

<h3 id="4-added-a-trust-layer">4. Added a trust layer</h3>

<p>This was the biggest shift.</p>

<p>Right after the hero, I added a section that answers:</p>
<ul>
  <li>who this is for</li>
  <li>what you get</li>
  <li>why it matters</li>
</ul>

<p>No fluff. No long paragraphs.</p>

<p>Then on the summit page:</p>
<ul>
  <li>added lightweight credibility framing</li>
  <li>tightened outcomes into short, practical statements</li>
</ul>

<p>The site now answers:</p>
<blockquote>
  <p>“Why should I care?” within 10 seconds</p>
</blockquote>

<hr />

<h2 id="what-i-learned">What I learned</h2>

<h3 id="1-visual-polish-doesnt-create-trust">1. Visual polish doesn’t create trust</h3>

<p>You can have:</p>
<ul>
  <li>clean layout</li>
  <li>good fonts</li>
  <li>nice colors</li>
</ul>

<p>…and still not convert.</p>

<p>Trust comes from:</p>
<ul>
  <li>clarity</li>
  <li>specificity</li>
  <li>restraint</li>
</ul>

<hr />

<h3 id="2-buttons-dont-need-effects--they-need-separation">2. Buttons don’t need effects — they need separation</h3>

<p>I tried glow. Didn’t like it.</p>

<p>The real fix was:</p>
<blockquote>
  <p>make the button feel like it exists on a different layer</p>
</blockquote>

<p>Simple shadow + solid fill beat everything else.</p>

<hr />

<h3 id="3-repetition-kills-credibility">3. Repetition kills credibility</h3>

<p>Adding a trust section exposed overlap with existing sections.</p>

<p>Lesson:</p>
<blockquote>
  <p>every section has to earn its space</p>
</blockquote>

<p>If two sections say the same thing, one has to go or tighten.</p>

<hr />

<h3 id="4-check-what-the-browser-is-actually-doing">4. Check what the browser is actually doing</h3>

<p>At one point I assumed fonts weren’t applying because of Next.js font output.</p>

<p>Reality:</p>
<blockquote>
  <p>computed styles told the truth</p>
</blockquote>

<hr />

<h2 id="next">Next</h2>

<p>The pieces are all there now:</p>
<ul>
  <li>hero</li>
  <li>CTA</li>
  <li>trust</li>
</ul>

<p>Next step is tightening flow:</p>

<blockquote>
  <p>what the user sees first → second → where they click</p>
</blockquote>

<p>That’s where this becomes a real conversion system.</p>

<hr />

<h2 id="closing-thought">Closing thought</h2>

<p>Today wasn’t about building more.</p>

<p>It was about removing doubt.</p>

<p>That’s the difference between:</p>
<ul>
  <li>a site that looks good</li>
  <li>and a site someone actually uses</li>
</ul>]]></content><author><name></name></author><category term="projects" /><category term="settled-field-platform" /><category term="nextjs" /><category term="ui" /><category term="ux" /><category term="conversion" /><category term="design" /><summary type="html"><![CDATA[TL;DR: Today wasn’t about adding features. It was about making the site feel real enough that someone would actually click.]]></summary></entry><entry><title type="html">2026-04-20 — 🚜 AssetTrack: Authentication Hardening and Returning to a Clean Baseline</title><link href="https://gregcurl.dev/projects/assettrack/assettrack-auth-reset-and-clean-baseline/" rel="alternate" type="text/html" title="2026-04-20 — 🚜 AssetTrack: Authentication Hardening and Returning to a Clean Baseline" /><published>2026-04-20T00:00:00+00:00</published><updated>2026-04-20T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/assettrack/assettrack-auth-reset-and-clean-baseline</id><content type="html" xml:base="https://gregcurl.dev/projects/assettrack/assettrack-auth-reset-and-clean-baseline/"><![CDATA[<p>TL;DR: Implemented a secure admin-controlled password reset without schema changes, fixed a subtle auth bug, reset the system to a clean production baseline, and removed friction from the operator experience.</p>

<p>Project page: <a href="/projects/assettrack/">AssetTrack</a>.</p>

<h2 id="context">Context</h2>

<p>After deployment and email delivery were stabilized, the next gap became clear:</p>

<p>Authentication recovery.</p>

<p>In a real system, users will forget passwords.<br />
If recovery is weak, the system is fragile.<br />
If recovery is too powerful, the system becomes unsafe.</p>

<p>This work focused on building a reset path that:</p>

<ul>
  <li>works offline</li>
  <li>stays inside local trust boundaries</li>
  <li>does not weaken authentication</li>
  <li>does not introduce new infrastructure dependencies</li>
</ul>

<p>At the same time, the system accumulated test data and UX friction that needed to be cleaned up before moving forward.</p>

<p>This was both a <strong>security pass</strong> and a <strong>reset to a clean operational state</strong>.</p>

<hr />

<h2 id="what-changed">What changed</h2>

<h3 id="admin-controlled-password-reset-no-schema-change">Admin-controlled password reset (no schema change)</h3>

<p>A reset flow was introduced with these properties:</p>

<ul>
  <li>admin triggers reset</li>
  <li>system generates a strong temporary password</li>
  <li>password is shown <strong>one time only</strong></li>
  <li>stored value remains hashed (no plaintext persistence)</li>
  <li>user must change password on next login</li>
</ul>

<p>The key constraint:</p>

<blockquote>
  <p>no schema change allowed</p>
</blockquote>

<p>Instead of adding a column, a marker prefix was applied to the existing password hash:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>assettrack-temp-password:&lt;hash&gt;
</code></pre></div></div>

<p>This allowed the system to:</p>

<ul>
  <li>detect temporary password state</li>
  <li>enforce password change</li>
  <li>avoid migrations entirely</li>
</ul>

<p>This kept the system stable while extending behavior.</p>

<hr />

<h3 id="the-bug-that-proved-the-design">The bug that proved the design</h3>

<p>Initial implementation failed at login.</p>

<p>Symptom:</p>

<ul>
  <li>temp password generated successfully</li>
  <li>login returned “Invalid login”</li>
</ul>

<p>Root cause:</p>

<ul>
  <li>authentication logic treated the prefixed value as a normal hash</li>
  <li>password verification never stripped the prefix</li>
</ul>

<p>Fix:</p>

<ul>
  <li>detect prefix during verification</li>
  <li>strip prefix</li>
  <li>pass underlying hash to verifier</li>
</ul>

<p>This was a small change, but important.</p>

<p>It reinforced a rule:</p>

<blockquote>
  <p>Any encoding or wrapping of stored values must be understood everywhere that value is read.</p>
</blockquote>

<hr />

<h3 id="forced-password-change-gated-access">Forced password change (gated access)</h3>

<p>After successful login with a temporary password:</p>

<ul>
  <li>user is blocked from normal routes</li>
  <li>user is redirected to password change</li>
  <li>access is restored only after successful update</li>
</ul>

<p>This ensures:</p>

<ul>
  <li>temporary credentials cannot be reused</li>
  <li>users do not operate the system in a degraded state</li>
</ul>

<hr />

<h3 id="secure-one-time-password-reveal">Secure one-time password reveal</h3>

<p>Temporary passwords are:</p>

<ul>
  <li>generated server-side</li>
  <li>returned only in the immediate response</li>
  <li>never stored in plaintext</li>
  <li>never retrievable later</li>
</ul>

<p>Hardening added:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Cache-Control: no-store</code></li>
  <li><code class="language-plaintext highlighter-rouge">Pragma: no-cache</code></li>
  <li><code class="language-plaintext highlighter-rouge">Expires: 0</code></li>
</ul>

<p>This reduces risk of:</p>

<ul>
  <li>browser caching</li>
  <li>proxy storage</li>
  <li>accidental leakage</li>
</ul>

<p>Delivery to the user is intentionally <strong>out-of-band</strong>:</p>

<ul>
  <li>in person</li>
  <li>phone</li>
  <li>trusted local channel</li>
</ul>

<p>The system does not attempt to deliver secrets.</p>

<hr />

<h3 id="account-state-remains-authoritative">Account state remains authoritative</h3>

<p>Reset does <strong>not</strong> modify account status.</p>

<p>If a user is:</p>

<ul>
  <li>disabled → they remain disabled after reset</li>
</ul>

<p>This avoids:</p>

<ul>
  <li>accidental reactivation</li>
  <li>bypass of administrative intent</li>
</ul>

<p>The system separates:</p>

<ul>
  <li><strong>authentication state</strong> (password)</li>
  <li><strong>account state</strong> (active/disabled)</li>
</ul>

<p>This is intentional.</p>

<hr />

<h3 id="clean-database-reset">Clean database reset</h3>

<p>At this point, test users and data were polluting the system.</p>

<p>Instead of partial cleanup:</p>

<ul>
  <li>database was backed up</li>
  <li>database was removed</li>
  <li>system was rebuilt clean</li>
</ul>

<p>Result:</p>

<ul>
  <li>no test artifacts</li>
  <li>no hidden references</li>
  <li>no inconsistent state</li>
</ul>

<p>Bootstrap was then used to:</p>

<ul>
  <li>create the initial admin user</li>
  <li>verify first-run behavior</li>
</ul>

<p>This restored the system to a <strong>true baseline</strong>.</p>

<hr />

<h3 id="manual-import-remains-intentional">Manual import remains intentional</h3>

<p>Inventory import was run manually after reset.</p>

<p>It is not part of startup.</p>

<p>This is by design.</p>

<p>If import ran automatically:</p>

<ul>
  <li>duplicate data would occur</li>
  <li>restarts would mutate state</li>
  <li>debugging would become difficult</li>
</ul>

<p>Import is treated as a <strong>controlled data operation</strong>, not a system requirement.</p>

<hr />

<h3 id="ux-friction-the-pointer-fix">UX friction: the pointer fix</h3>

<p>One small issue had outsized impact:</p>

<p>Buttons did not show a pointer cursor.</p>

<p>Effect:</p>

<ul>
  <li>UI felt unresponsive</li>
  <li>operators questioned whether actions were clickable</li>
</ul>

<p>Fix:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">button</span> <span class="p">{</span> <span class="nl">cursor</span><span class="p">:</span> <span class="nb">pointer</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>This did not change behavior.</p>

<p>It changed <strong>perception of reliability</strong>.</p>

<hr />

<h2 id="what-i-learned">What I learned</h2>

<h3 id="1-schema-changes-are-expensive--avoid-when-possible">1. Schema changes are expensive — avoid when possible</h3>

<p>The reset flow could have introduced new columns.</p>

<p>It didn’t.</p>

<p>By reusing existing structure carefully, the system gained capability without increasing migration risk.</p>

<hr />

<h3 id="2-authentication-bugs-hide-in-edge-encoding">2. Authentication bugs hide in edge encoding</h3>

<p>The system worked — except for one detail:</p>

<p>A prefix.</p>

<p>That small mismatch broke the entire flow.</p>

<p>Lesson:</p>

<blockquote>
  <p>Every transformation of stored data must be reversible at every read point.</p>
</blockquote>

<hr />

<h3 id="3-separation-of-concerns-matters">3. Separation of concerns matters</h3>

<p>Password reset did not:</p>

<ul>
  <li>enable accounts</li>
  <li>change roles</li>
  <li>alter workflow</li>
</ul>

<p>That separation keeps the system predictable.</p>

<hr />

<h3 id="4-clean-state-beats-partial-cleanup">4. Clean state beats partial cleanup</h3>

<p>Deleting test users manually risks:</p>

<ul>
  <li>orphaned data</li>
  <li>inconsistent references</li>
  <li>hidden bugs</li>
</ul>

<p>Resetting the database created:</p>

<ul>
  <li>a known-good baseline</li>
  <li>a reproducible starting point</li>
</ul>

<hr />

<h3 id="5-ux-signals-are-part-of-system-correctness">5. UX signals are part of system correctness</h3>

<p>The pointer cursor fix did not change logic.</p>

<p>But it changed:</p>

<ul>
  <li>operator confidence</li>
  <li>perceived responsiveness</li>
  <li>usability under pressure</li>
</ul>

<p>That matters in a field system.</p>

<hr />

<h2 id="next">Next</h2>

<p><strong>Issue 26-152 — Improve Admin User Reset Flow Clarity</strong></p>

<p>The system works correctly, but the UI still requires operator thinking:</p>

<ul>
  <li>user state is not obvious</li>
  <li>reset vs enable sequence is not guided</li>
</ul>

<p>Next step:</p>

<ul>
  <li>make account state explicit</li>
  <li>guide correct next action</li>
  <li>reduce cognitive load</li>
</ul>

<hr />

<h2 id="bottom-line">Bottom line</h2>

<p>The system is now:</p>

<ul>
  <li>secure in authentication recovery</li>
  <li>free of test data</li>
  <li>operating from a clean baseline</li>
  <li>more trustworthy to the operator</li>
</ul>

<p>The biggest changes were not structural.</p>

<p>They were about:</p>

<ul>
  <li>respecting constraints</li>
  <li>fixing small but critical gaps</li>
  <li>and making the system behave predictably</li>
</ul>

<p>That is what turns working code into a reliable system.</p>]]></content><author><name></name></author><category term="projects" /><category term="assettrack" /><category term="assettrack" /><category term="authentication" /><category term="security" /><category term="sqlite" /><category term="ux" /><summary type="html"><![CDATA[TL;DR: Implemented a secure admin-controlled password reset without schema changes, fixed a subtle auth bug, reset the system to a clean production baseline, and removed friction from the operator experience.]]></summary></entry><entry><title type="html">2026-04-12 — 🧭 Settled on the Field: Admin Control Layer Complete</title><link href="https://gregcurl.dev/projects/settled-field-platform/admin-control-layer-complete/" rel="alternate" type="text/html" title="2026-04-12 — 🧭 Settled on the Field: Admin Control Layer Complete" /><published>2026-04-12T00:00:00+00:00</published><updated>2026-04-12T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/settled-field-platform/admin-control-layer-complete</id><content type="html" xml:base="https://gregcurl.dev/projects/settled-field-platform/admin-control-layer-complete/"><![CDATA[<p><strong>TL;DR:</strong> The system is no longer just pages and forms — it now has real operator control. Admin access, user lifecycle, and production auth are fully live.</p>

<p>Project page: <a href="/projects/settled-field-platform/">Settled Field Platform</a></p>

<hr />

<h2 id="context">Context</h2>

<p>Up until now, this build was moving toward something real, but not fully operational.</p>

<p>We had:</p>
<ul>
  <li>a working funnel</li>
  <li>a real registration flow</li>
  <li>persistence in place</li>
</ul>

<p>But we didn’t yet have:</p>
<ul>
  <li>controlled access</li>
  <li>operator autonomy</li>
  <li>a way to safely manage who can use the system</li>
</ul>

<p>That’s what Milestone 3 was about.</p>

<p>And today, that layer is done.</p>

<hr />

<h2 id="what-changed">What changed</h2>

<p>This milestone introduced the full <strong>admin control layer</strong>.</p>

<h3 id="1-real-authentication-db-backed">1. Real authentication (DB-backed)</h3>
<ul>
  <li>Admin users stored in Postgres</li>
  <li>Roles:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">owner</code></li>
      <li><code class="language-plaintext highlighter-rouge">admin</code></li>
    </ul>
  </li>
  <li>Passwords hashed properly</li>
  <li>Session cookies signed and verified per request</li>
</ul>

<p>This is no longer “fake login.”<br />
It’s real auth.</p>

<hr />

<h3 id="2-full-admin-lifecycle">2. Full admin lifecycle</h3>

<p>We now have the complete flow:</p>

<ul>
  <li>Owner creates admin users</li>
  <li>Users can request access (<code class="language-plaintext highlighter-rouge">/admin/request-access</code>)</li>
  <li>Owner reviews requests (<code class="language-plaintext highlighter-rouge">/admin/requests</code>)</li>
  <li>Owner can:
    <ul>
      <li>approve</li>
      <li>deny</li>
      <li>disable</li>
      <li>permanently remove</li>
    </ul>
  </li>
</ul>

<p>With guardrails:</p>
<ul>
  <li>system prevents deleting the last owner</li>
  <li>access is validated against DB on every request</li>
</ul>

<p>This is a <strong>controlled system</strong>, not a free-for-all.</p>

<hr />

<h3 id="3-production-bootstrap-the-real-lesson">3. Production bootstrap (the real lesson)</h3>

<p>Getting this live surfaced something important:</p>

<blockquote>
  <p>A working system locally is not the same as a working system in production.</p>
</blockquote>

<p>To make admin login work on Vercel, three things had to be true:</p>

<ul>
  <li>correct <code class="language-plaintext highlighter-rouge">DATABASE_URL</code></li>
  <li>correct production database (not a lookalike)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">ADMIN_SESSION_SECRET</code> present</strong></li>
</ul>

<p>That last one was the trap.</p>

<p>Without it:</p>
<ul>
  <li>login “worked”</li>
  <li>but session creation failed</li>
  <li>result: <code class="language-plaintext highlighter-rouge">Unauthorized</code></li>
</ul>

<p>Once that was fixed, everything snapped into place.</p>

<hr />

<h3 id="4-first-real-operator-moment">4. First real operator moment</h3>

<p>I created the first production owner via CLI, then logged in through the live app.</p>

<p>That’s the moment this stopped being:</p>
<blockquote>
  <p>“a thing I’m building”</p>
</blockquote>

<p>and became:</p>
<blockquote>
  <p>“a system that can be operated”</p>
</blockquote>

<p>Then I brought in a second user as admin.</p>

<p>No CLI.
No database work.
No dev intervention.</p>

<p>Just:</p>
<ul>
  <li>create user</li>
  <li>send login</li>
  <li>system handles the rest</li>
</ul>

<p>That’s the shift.</p>

<hr />

<h2 id="what-i-learned">What I learned</h2>

<h3 id="1-auth-is-not-just-login">1. Auth is not just login</h3>
<p>You need:</p>
<ul>
  <li>identity (DB)</li>
  <li>verification (password)</li>
  <li>session (cookie)</li>
  <li><strong>signature (secret)</strong></li>
</ul>

<p>Miss one → system fails in non-obvious ways</p>

<hr />

<h3 id="2-production-truth-matters">2. Production truth matters</h3>
<p>You can’t fake:</p>
<ul>
  <li>environment variables</li>
  <li>database wiring</li>
  <li>session handling</li>
</ul>

<p>Those seams will break if they’re not real.</p>

<hr />

<h3 id="3-control-is-what-makes-a-system-usable">3. Control is what makes a system usable</h3>
<p>Pages don’t make a product.</p>

<p>Control does.</p>

<p>Now the system can:</p>
<ul>
  <li>onboard users</li>
  <li>restrict access</li>
  <li>remove access</li>
  <li>operate without the developer</li>
</ul>

<p>That’s the difference between:</p>
<blockquote>
  <p>demo<br />
and<br />
product</p>
</blockquote>

<hr />

<h2 id="next">Next</h2>

<p>Milestone 4:</p>

<p><strong>Payment Hardening (Stripe)</strong></p>

<p>Up next:</p>
<ul>
  <li>real checkout session</li>
  <li>payment verification</li>
  <li>tying money to registration truth</li>
</ul>

<p>That’s where this becomes:</p>
<blockquote>
  <p>not just usable<br />
but valuable</p>
</blockquote>

<hr />

<p>This was a big one.</p>

<p>The system is now:</p>
<ul>
  <li>structured</li>
  <li>controlled</li>
  <li>live</li>
</ul>

<p>Next step:
make it real revenue.</p>]]></content><author><name></name></author><category term="projects" /><category term="settled-field-platform" /><category term="admin" /><category term="authentication" /><category term="postgres" /><category term="vercel" /><category term="milestone3" /><summary type="html"><![CDATA[TL;DR: The system is no longer just pages and forms — it now has real operator control. Admin access, user lifecycle, and production auth are fully live.]]></summary></entry><entry><title type="html">2026-04-12 — 🧭 Settled on the Field: Admin Control Layer Complete</title><link href="https://gregcurl.dev/projects/settled-field-platform/settled-field-admin-control-layer-complete/" rel="alternate" type="text/html" title="2026-04-12 — 🧭 Settled on the Field: Admin Control Layer Complete" /><published>2026-04-12T00:00:00+00:00</published><updated>2026-04-12T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/settled-field-platform/settled-field-admin-control-layer-complete</id><content type="html" xml:base="https://gregcurl.dev/projects/settled-field-platform/settled-field-admin-control-layer-complete/"><![CDATA[<p>TL;DR: The system now has a real admin layer—secure login, multi-user access, and a working attendee list backed by durable data.</p>

<p>Project page: <a href="/projects/settled-field-platform/">Settled Field Platform</a>.</p>

<hr />

<h2 id="context">Context</h2>

<p>Up to this point, the project was a strong front-end funnel:</p>

<p>Landing → Summit → Register → Success → Checkout (stub)</p>

<p>It looked real. It behaved well. But it was missing something critical:</p>

<p>Operational control.</p>

<p>There was no way for Bill (or anyone helping him) to actually see or manage what was happening inside the system.</p>

<p>That changed today.</p>

<hr />

<h2 id="what-changed">What Changed</h2>

<p>Milestone 3 is now complete.</p>

<p>That includes:</p>

<ul>
  <li>Protected admin access</li>
  <li>Multi-user admin authentication (no shared password)</li>
  <li>Durable registration storage (real data, not just a form submission)</li>
  <li>Attendee list view backed by the database</li>
  <li>A dashboard shell that functions as a control surface</li>
</ul>

<p>The key shift:</p>

<p>The system moved from collecting data to owning data.</p>

<p>Registrations are no longer ephemeral—they are stored, structured, and visible.</p>

<p>Admins are no longer theoretical—they can log in and operate.</p>

<hr />

<h2 id="what-i-learned">What I Learned</h2>

<p>The biggest lesson here wasn’t technical—it was architectural discipline.</p>

<p>I initially tried to build the attendee list first.</p>

<p>That failed for the right reason:
the system didn’t actually store attendees yet.</p>

<p>So I had to step back and build the persistence layer first.</p>

<p>That decision:</p>
<ul>
  <li>avoided fake UI</li>
  <li>avoided immediate rework</li>
  <li>created a foundation for everything that comes next</li>
</ul>

<p>Another key takeaway:</p>

<p>Authentication doesn’t need to be complex to be correct.</p>

<p>Instead of introducing a full auth system, I implemented:</p>
<ul>
  <li>DB-backed users</li>
  <li>hashed passwords</li>
  <li>signed session cookies</li>
  <li>server-side route protection</li>
</ul>

<p>Simple, controlled, and appropriate for the use case.</p>

<hr />

<h2 id="whats-next">What’s Next</h2>

<p>Next is Milestone 4: Payment Hardening.</p>

<p>That includes:</p>
<ul>
  <li>wiring real Stripe checkout</li>
  <li>handling webhook events</li>
  <li>linking registration to payment state</li>
  <li>ensuring the system reflects who has actually paid</li>
</ul>

<p>Right now:
we can see who registered.</p>

<p>Next:
we need to know who committed.</p>

<hr />

<h2 id="closing-thought">Closing Thought</h2>

<p>This is the milestone where the project stopped being a site and became a system.</p>

<p>Before:</p>
<ul>
  <li>pages</li>
  <li>forms</li>
  <li>flow</li>
</ul>

<p>Now:</p>
<ul>
  <li>data</li>
  <li>control</li>
  <li>operators</li>
</ul>

<p>That is the difference between something that looks complete and something that can actually run an event.</p>]]></content><author><name></name></author><category term="projects" /><category term="settled-field-platform" /><category term="admin" /><category term="nextjs" /><category term="postgres" /><category term="auth" /><category term="mvp" /><category term="systems" /><summary type="html"><![CDATA[TL;DR: The system now has a real admin layer—secure login, multi-user access, and a working attendee list backed by durable data.]]></summary></entry><entry><title type="html">2026-04-11 — 🏗️ gregcurl.dev Rebuild: Milestone 0 Complete</title><link href="https://gregcurl.dev/projects/gregcurl-dev-rebuild/gregcurl-rebuild-milestone-0-complete/" rel="alternate" type="text/html" title="2026-04-11 — 🏗️ gregcurl.dev Rebuild: Milestone 0 Complete" /><published>2026-04-11T00:00:00+00:00</published><updated>2026-04-11T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/gregcurl-dev-rebuild/gregcurl-rebuild-milestone-0-complete</id><content type="html" xml:base="https://gregcurl.dev/projects/gregcurl-dev-rebuild/gregcurl-rebuild-milestone-0-complete/"><![CDATA[<p><strong>TL;DR:</strong> Milestone 0 is complete. The rebuild now has a clean architecture, hosting strategy, and security baseline. Next step is building the actual application.</p>

<p>Project page: <a href="/projects/gregcurl-dev-rebuild/">gregcurl.dev Rebuild</a></p>

<hr />

<h2 id="context">Context</h2>

<p>I’m rebuilding my portfolio into a working consulting platform.</p>

<p>The goal isn’t a better-looking website — it’s a system that:</p>
<ul>
  <li>demonstrates real capability</li>
  <li>captures leads</li>
  <li>supports direct engagement</li>
  <li>eventually enables payment</li>
</ul>

<p>Before writing any application code, I focused on getting the foundation right.</p>

<hr />

<h2 id="what-changed">What changed</h2>

<p>Milestone 0 is now complete:</p>

<ul>
  <li>Established canonical repository (<code class="language-plaintext highlighter-rouge">gacurl-web</code>)</li>
  <li>Defined hosting split (Vercel for frontend, DigitalOcean for systems like AssetTrack)</li>
  <li>Documented DNS cutover strategy</li>
  <li>Created environment variable contract (<code class="language-plaintext highlighter-rouge">.env.example</code>)</li>
  <li>Implemented CI/CD and security baseline:
    <ul>
      <li>GitHub Actions pipeline</li>
      <li>Dependency Review</li>
      <li>CodeQL scanning</li>
    </ul>
  </li>
</ul>

<p>This means the project now has a stable, security-conscious starting point.</p>

<hr />

<h2 id="what-i-learned">What I learned</h2>

<p>The biggest lesson is that <strong>discipline early prevents rework later</strong>.</p>

<p>It’s tempting to jump straight into building pages, but that usually leads to:</p>
<ul>
  <li>unclear architecture</li>
  <li>messy environment handling</li>
  <li>security gaps</li>
  <li>rework during deployment</li>
</ul>

<p>By locking these decisions first, the rest of the build becomes much more straightforward.</p>

<hr />

<h2 id="next">Next</h2>

<p>Milestone 1: Scaffold the Next.js application.</p>

<p>This will:</p>
<ul>
  <li>activate the CI pipeline with real builds</li>
  <li>establish the development loop</li>
  <li>create the first visible version of the platform</li>
</ul>

<p>From here, the project shifts from planning → execution.</p>]]></content><author><name></name></author><category term="projects" /><category term="gregcurl-dev-rebuild" /><category term="nextjs" /><category term="architecture" /><category term="ci-cd" /><category term="security" /><category term="consulting-platform" /><summary type="html"><![CDATA[TL;DR: Milestone 0 is complete. The rebuild now has a clean architecture, hosting strategy, and security baseline. Next step is building the actual application.]]></summary></entry><entry><title type="html">2026-04-11 — 🧭 Settled on the Field: From Form to Flow</title><link href="https://gregcurl.dev/projects/settled-field-platform/from-form-to-flow/" rel="alternate" type="text/html" title="2026-04-11 — 🧭 Settled on the Field: From Form to Flow" /><published>2026-04-11T00:00:00+00:00</published><updated>2026-04-11T00:00:00+00:00</updated><id>https://gregcurl.dev/projects/settled-field-platform/from-form-to-flow</id><content type="html" xml:base="https://gregcurl.dev/projects/settled-field-platform/from-form-to-flow/"><![CDATA[<p><strong>TL;DR:</strong> The site stopped being “just pages” today. Registration is real, payment is scaffolded, and the funnel now has forward motion.</p>

<p>Project page: <a href="/projects/settled-field-platform/">Settled Field Platform</a>.</p>

<hr />

<h2 id="context">Context</h2>

<p>Up until now, everything looked good — but it wasn’t <em>real</em> yet.</p>

<p>Milestone 1 gave me:</p>
<ul>
  <li>a clean landing page</li>
  <li>a summit page that builds trust</li>
  <li>a registration page that <em>looked</em> like the next step</li>
</ul>

<p>But it was still a surface.</p>

<p>Today was about turning that surface into behavior:</p>
<ul>
  <li>capturing intent</li>
  <li>holding it</li>
  <li>moving the user forward</li>
</ul>

<p>Without overbuilding the backend.</p>

<hr />

<h2 id="what-changed">What changed</h2>

<p>Two major things happened.</p>

<h3 id="1-registration-became-real">1. Registration became real</h3>

<p>The form now:</p>
<ul>
  <li>enforces required fields</li>
  <li>validates input server-side</li>
  <li>normalizes data (email)</li>
  <li>stores a short-lived draft (HTTP-only cookie)</li>
</ul>

<p>That last part matters.</p>

<p>Instead of jumping straight into a database, I used a <strong>draft model</strong>:</p>
<ul>
  <li>temporary</li>
  <li>server-owned</li>
  <li>just enough to bridge into payment</li>
</ul>

<p>This keeps the system light while still making it <em>real</em>.</p>

<hr />

<h3 id="2-payment-became-a-defined-path">2. Payment became a defined path</h3>

<p>Stripe isn’t live yet — and that’s intentional.</p>

<p>What <em>is</em> live:</p>
<ul>
  <li>a server-side checkout entry point</li>
  <li>environment-driven Stripe config shape</li>
  <li>a controlled “stub mode” when keys aren’t present</li>
</ul>

<p>Flow now looks like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Register → Payment-ready → Checkout seam → Stub (for now)
</code></pre></div></div>

<p>No fake success.<br />
No dead ends.<br />
No pretending.</p>

<p>Just forward motion.</p>

<hr />

<h2 id="what-i-learned">What I learned</h2>

<h3 id="1-dont-solve-the-final-system-too-early">1. Don’t solve the final system too early</h3>

<p>It’s tempting to jump straight into:</p>
<ul>
  <li>database models</li>
  <li>payment records</li>
  <li>webhook handling</li>
</ul>

<p>But that would’ve slowed everything down.</p>

<p>Instead:</p>
<ul>
  <li>form → server action</li>
  <li>draft → cookie</li>
  <li>checkout → scaffold</li>
</ul>

<p>Each step supports the next, without locking anything in.</p>

<hr />

<h3 id="2-design-consistency-matters-more-than-features">2. Design consistency matters more than features</h3>

<p>The biggest improvement today wasn’t technical.</p>

<p>It was removing the <strong>“app UI” feel</strong> from <code class="language-plaintext highlighter-rouge">/register</code>.</p>

<p>Once I aligned it with the same:</p>
<ul>
  <li>spacing</li>
  <li>typography</li>
  <li>section structure</li>
</ul>

<p>…it stopped feeling like a form and started feeling like a <strong>step in a journey</strong>.</p>

<p>That’s the difference between:</p>
<ul>
  <li>a page</li>
  <li>and a conversion system</li>
</ul>

<hr />

<h3 id="3-forward-motion-is-everything">3. Forward motion is everything</h3>

<p>Every step now answers:</p>

<blockquote>
  <p>“What happens next?”</p>
</blockquote>

<ul>
  <li>submit → you’re ready for payment</li>
  <li>continue → you enter checkout (or stub)</li>
  <li>no confusion, no guessing</li>
</ul>

<p>Even without Stripe live, the system <em>moves</em>.</p>

<hr />

<h2 id="next">Next</h2>

<p>Two paths, depending on timing:</p>

<h3 id="if-stripe-keys-arrive">If Stripe keys arrive:</h3>
<ul>
  <li>wire real Checkout</li>
  <li>complete payment loop</li>
  <li>introduce confirmation state</li>
</ul>

<h3 id="if-not">If not:</h3>
<ul>
  <li>build Confirmation page (Issue 2-4)</li>
  <li>complete the visible funnel</li>
  <li>keep momentum</li>
</ul>

<hr />

<p>Today was the shift.</p>

<p>This isn’t a website anymore.</p>

<p>It’s a system.</p>]]></content><author><name></name></author><category term="projects" /><category term="settled-field-platform" /><category term="nextjs" /><category term="stripe" /><category term="product-design" /><category term="mvp" /><category term="conversion" /><summary type="html"><![CDATA[TL;DR: The site stopped being “just pages” today. Registration is real, payment is scaffolded, and the funnel now has forward motion.]]></summary></entry></feed>