<?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: BlazOrbit</title>
    <description>The latest articles on DEV Community by BlazOrbit (@blazorbit).</description>
    <link>https://dev.to/blazorbit</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908471%2Ffa3f179a-bf9a-4768-97bd-0237256cd6f9.png</url>
      <title>DEV Community: BlazOrbit</title>
      <link>https://dev.to/blazorbit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/blazorbit"/>
    <language>en</language>
    <item>
      <title>AutoDream Validated What I Was Already Building: An Agent Memory System That Works</title>
      <dc:creator>BlazOrbit</dc:creator>
      <pubDate>Fri, 08 May 2026 04:10:59 +0000</pubDate>
      <link>https://dev.to/blazorbit/autodream-validated-what-i-was-already-building-an-agent-memory-system-that-works-40d7</link>
      <guid>https://dev.to/blazorbit/autodream-validated-what-i-was-already-building-an-agent-memory-system-that-works-40d7</guid>
      <description>&lt;p&gt;A few days ago, the documentation for &lt;strong&gt;Claude Code AutoDream&lt;/strong&gt; circulated — Anthropic's new feature that consolidates Claude's automatic memory across sessions. The technical community received it as an innovation. I read it with a different feeling: &lt;em&gt;validation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Not because AutoDream is minor — it's a major step toward making AI agents maintain long-term coherence — but because &lt;strong&gt;a few months ago, Claude and I co-designed an equivalent system (and in some respects a more structured one) for the BlazOrbit project&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on origins:&lt;/strong&gt; It wasn't just me drawing the memory architecture on paper and imposing it on the agent. It was Claude herself who, in an early session, proposed an incremental work system when I asked her to design a protocol for long development sessions. Her first version was fragmented: &lt;code&gt;tasks/&lt;/code&gt;, &lt;code&gt;notes/&lt;/code&gt;, &lt;code&gt;ideas/&lt;/code&gt; directories, scattered state files. I made her rethink it. I asked for something simpler — a single living document that would act as a continuous session. From that conversation, &lt;code&gt;.exclude/plans/next-session.md&lt;/code&gt; was born: a rolling file with no &lt;code&gt;done/&lt;/code&gt;, no &lt;code&gt;archive/&lt;/code&gt;, no &lt;code&gt;ideas/&lt;/code&gt; — just candidates, deferred items with an aging counter, and loose notes. Everything else (&lt;code&gt;AGENTS.md&lt;/code&gt; as index, &lt;code&gt;.exclude/agents/&lt;/code&gt; as depth, &lt;code&gt;WORKFLOW.md&lt;/code&gt; as contract) settled in over subsequent sessions, but the seed was an iteration between human and AI, not a unilateral decree.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AutoDream didn't teach me what to do; it gave me the vocabulary to explain why a system born from that iteration works.&lt;/p&gt;

&lt;p&gt;This post is that exercise: mapping AutoDream's phases against my current protocol, showing where they converge, and explaining why certain design decisions are what really matter when an agent's memory grows beyond a couple of sessions.&lt;/p&gt;

&lt;p&gt;To avoid staying in theory, I audited &lt;strong&gt;Claude's actual memory&lt;/strong&gt; from my project — the files Claude has been automatically writing in &lt;code&gt;~/.claude/projects/&amp;lt;project&amp;gt;/memory/&lt;/code&gt;. What I found confirms the thesis and adds nuances that only become visible when you contrast the agent's memory against the system we co-designed.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Memory Architecture in One Sentence
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AGENTS.md&lt;/strong&gt; is the startup index; &lt;strong&gt;&lt;code&gt;.exclude/agents/&lt;/code&gt;&lt;/strong&gt; are the topic files; &lt;strong&gt;&lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt;&lt;/strong&gt; is the orchestration protocol; &lt;strong&gt;&lt;code&gt;.exclude/plans/next-session.md&lt;/code&gt;&lt;/strong&gt; is the accumulated signal transcript; and &lt;strong&gt;&lt;code&gt;.agents/skills/&lt;/code&gt;&lt;/strong&gt; are the modular exportable capabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Translated into AutoDream's language:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AutoDream Layer&lt;/th&gt;
&lt;th&gt;Equivalent in My System&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MEMORY.md&lt;/code&gt; (index, &amp;lt;200 lines)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; (index, soft cap 350 lines)&lt;/td&gt;
&lt;td&gt;What the agent reads on wake-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Topic files (&lt;code&gt;debugging.md&lt;/code&gt;, &lt;code&gt;api-conventions.md&lt;/code&gt;…)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.exclude/agents/&amp;lt;area&amp;gt;.md&lt;/code&gt; (&lt;code&gt;css-architecture.md&lt;/code&gt;, &lt;code&gt;testing.md&lt;/code&gt;, &lt;code&gt;state-axes.md&lt;/code&gt;…)&lt;/td&gt;
&lt;td&gt;Deep knowledge by domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session transcripts (JSONL)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.exclude/plans/next-session.md&lt;/code&gt; + &lt;code&gt;.exclude/tasks/active/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Signal accumulated between sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dream system prompt&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Consolidation protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.agents/skills/blazorbit-user/&lt;/code&gt; + &lt;code&gt;docs/ARCHITECTURE.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Reusable capabilities + public face&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Real Audit: What's in Claude's Memory for My Project
&lt;/h2&gt;

&lt;p&gt;Before continuing with theory, let's look at data. This is what Claude has automatically accumulated in &lt;code&gt;memory/&lt;/code&gt; for BlazOrbit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;memory/
├── MEMORY.md                           (6 lines, 1,039 bytes)
├── startup_protocol.md                 (20 lines, 2,518 bytes)
├── reference_workflow_docs.md          (18 lines, 1,407 bytes)
├── user_role.md                        (11 lines, 837 bytes)
├── reference_rs0041_autogen.md         (12 lines, 1,516 bytes)
├── changelog_policy.md                 (9 lines, 784 bytes)
└── bobinitializer_static_ssr.md        (28 lines, 1,881 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The index (&lt;code&gt;MEMORY.md&lt;/code&gt;) is impeccable: 6 lines, 6 pointers, not a word wasted. The topic files have structured metadata (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;originSessionId&lt;/code&gt;). There's no &lt;code&gt;random-notes.md&lt;/code&gt;, no contradictions, no relative dates like "yesterday".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But there's something more interesting: Claude didn't invent this structure ex nihilo. She learned it from the system we co-designed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compare the &lt;code&gt;startup_protocol.md&lt;/code&gt; from Claude's memory with &lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt; from the repo. They say practically the same thing: read &lt;code&gt;AGENTS.md&lt;/code&gt;, then &lt;code&gt;WORKFLOW.md&lt;/code&gt;, then &lt;code&gt;next-session.md&lt;/code&gt;, then &lt;code&gt;tasks/active/&lt;/code&gt;. The agent's memory is a &lt;strong&gt;mirror&lt;/strong&gt; of the protocol we sedimented across sessions.&lt;/p&gt;

&lt;p&gt;This is the first important lesson: &lt;strong&gt;when a human and an agent co-design a clear structure, automatic memory converges toward it&lt;/strong&gt;. Without that structure, the agent's memory generates noise. With it, it generates a reflection.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Claude's Memory Captured Well (That Isn't in AGENTS.md)
&lt;/h3&gt;

&lt;p&gt;Automatic memory shines at capturing &lt;strong&gt;one-off decisions that arose in session&lt;/strong&gt; that the human never formalized in a permanent doc:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bobinitializer_static_ssr.md&lt;/code&gt;&lt;/strong&gt;: The exact workaround to prevent &lt;code&gt;BOBInitializer&lt;/code&gt; from hiding &lt;code&gt;ChildContent&lt;/code&gt; in static SSR, with the Razor snippet and explanation of why. This isn't in &lt;code&gt;AGENTS.md&lt;/code&gt; or &lt;code&gt;docs/ARCHITECTURE.md&lt;/code&gt;. It surfaced in a debugging session and Claude caught it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;reference_rs0041_autogen.md&lt;/code&gt;&lt;/strong&gt;: The detail that &lt;code&gt;PublicAPI.Unshipped.txt&lt;/code&gt; isn't enough for Razor; you also need &lt;code&gt;GlobalSuppressions.cs&lt;/code&gt; with one attribute per component, and that there's an autogen project that reconciles this. A toolchain detail I mentioned in passing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;changelog_policy.md&lt;/code&gt;&lt;/strong&gt;: The rule that &lt;code&gt;[Unreleased]&lt;/code&gt; stays empty until release 1.0.0. A process decision I made but never wrote down anywhere committed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three files are &lt;strong&gt;pure value&lt;/strong&gt;. They're things I know but didn't formally document because they were "obvious" or recent. Automatic memory acts here as a &lt;strong&gt;conversational context backup&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Claude's Memory Unnecessarily Duplicates
&lt;/h3&gt;

&lt;p&gt;Now the problematic part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;startup_protocol.md&lt;/code&gt;&lt;/strong&gt; duplicates &lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt; (the "Session start" section).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;reference_workflow_docs.md&lt;/code&gt;&lt;/strong&gt; duplicates &lt;code&gt;.exclude/agents/INDEX.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt; itself (the "Project layout" section).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;user_role.md&lt;/code&gt;&lt;/strong&gt; duplicates the "Language policy" section of &lt;code&gt;AGENTS.md&lt;/code&gt; and the "I don't commit" rule from &lt;code&gt;WORKFLOW.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three files add no new information; they &lt;strong&gt;rephrase&lt;/strong&gt; it. They're not contradictory — Claude is too good for that — but they are &lt;strong&gt;redundant&lt;/strong&gt;. They consume context tokens that could be used for real work.&lt;/p&gt;

&lt;p&gt;This is where the AutoDream parallel comes in: if I let this accumulate for 30 more sessions, those 3 files would keep growing in granularity and eventually diverge from the repo's source of truth. My system invalidates them by design (&lt;code&gt;verify against live code before claiming&lt;/code&gt;), but AutoDream would be the one having to prune them if I didn't have that protocol.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Claude's Memory Doesn't Know (Because My System Handles It First)
&lt;/h3&gt;

&lt;p&gt;Today, during this session, we created a complete plan for &lt;code&gt;BlazOrbit.Charts&lt;/code&gt; in &lt;code&gt;.exclude/plans/bob-charts.md&lt;/code&gt; (237 lines, 7-phase structure). Claude's memory doesn't know about it. Her last write was May 7, 2026; the plan is from May 8.&lt;/p&gt;

&lt;p&gt;Why doesn't it matter? Because my system doesn't depend on Claude remembering. The plan lives in the repo, in a file the agent reads at session start (&lt;code&gt;next-session.md&lt;/code&gt; points to it, or directly to the file if it's active). &lt;strong&gt;The agent's memory is a cache; the repo is the database.&lt;/strong&gt; If the cache falls behind, the startup protocol refreshes it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase by Phase: The Same Cycle, Named Differently
&lt;/h2&gt;

&lt;p&gt;AutoDream describes four phases. My protocol doesn't name them that way, but it executes the same operations in the same order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Orientation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AutoDream:&lt;/strong&gt; &lt;code&gt;ls&lt;/code&gt; of the memory directory, read &lt;code&gt;MEMORY.md&lt;/code&gt;, skim existing topic files to avoid duplicates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My system:&lt;/strong&gt; At the start of each session, the agent executes sequentially:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;git branch --show-current&lt;/code&gt; (branch context).&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;AGENTS.md&lt;/code&gt; root.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read &lt;code&gt;.exclude/plans/next-session.md&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Glance at &lt;code&gt;.exclude/tasks/active/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Attend to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The subtle but important difference: my orientation includes &lt;strong&gt;version control state&lt;/strong&gt; as the first step. Memory doesn't live in a vacuum; it lives in a repo with a branch policy. If the agent doesn't know what branch it's on before editing, architectural memory becomes dangerous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit evidence:&lt;/strong&gt; Claude's memory (&lt;code&gt;startup_protocol.md&lt;/code&gt;) has internalized this exact list. The agent learned the orientation I imposed and now repeats it as its own memory. It's a perfect example of how a well-designed human protocol becomes the agent's "dream."&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Gather Signal
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AutoDream:&lt;/strong&gt; Search session transcripts for specific patterns: user corrections, "remember this," architectural decisions, recurring themes. Uses narrow &lt;code&gt;grep&lt;/code&gt;, not exhaustive reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My system:&lt;/strong&gt; Signal isn't searched in transcripts (I don't store them as JSONL; Claude does, but I don't depend on them). It lives in two places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.exclude/plans/next-session.md&lt;/code&gt;&lt;/strong&gt;: a rolling backlog with sections &lt;em&gt;Candidate streams&lt;/em&gt;, &lt;em&gt;Deferred&lt;/em&gt; (with a &lt;code&gt;deferred: N&lt;/code&gt; counter), and &lt;em&gt;Loose notes / ideas&lt;/em&gt;. It's the equivalent of the "daily logs" AutoDream mentions as priority #1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The live code itself&lt;/strong&gt;: the protocol's hard rule is &lt;em&gt;"I verify before claiming."&lt;/em&gt; If a memory assertion is going to guide an action, the agent must validate it against the code using &lt;code&gt;Glob&lt;/code&gt;/&lt;code&gt;Grep&lt;/code&gt;/&lt;code&gt;Read&lt;/code&gt; before acting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Audit evidence:&lt;/strong&gt; The three memory files that deliver the most value (&lt;code&gt;bobinitializer_static_ssr.md&lt;/code&gt;, &lt;code&gt;reference_rs0041_autogen.md&lt;/code&gt;, &lt;code&gt;changelog_policy.md&lt;/code&gt;) are exactly the kind of signal that surfaces in session and that a human doesn't formalize. My &lt;code&gt;next-session.md&lt;/code&gt; has a "Loose notes / ideas" section for this, but Claude's memory does complementary work: it converts those loose notes into permanent topic files. On a project without my protocol, those three details would have been lost. In my project, both systems coexist: the human jots in &lt;code&gt;next-session.md&lt;/code&gt;, the agent consolidates in &lt;code&gt;memory/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Consolidation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AutoDream:&lt;/strong&gt; Merge new signal into existing topic files, convert relative dates to absolute ones, remove contradicted facts, prune obsolete memories, merge overlapping entries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My system:&lt;/strong&gt; Consolidation is not a periodic batch; it's a &lt;strong&gt;continuous rule&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"After modifying code: if the change touches a contract documented in AGENTS.md or &lt;code&gt;.exclude/agents/&lt;/code&gt;, update the corresponding section in the same turn."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's no sleep phase. Every time code changes, memory updates &lt;em&gt;in place&lt;/em&gt;. If I discover &lt;em&gt;drift&lt;/em&gt; between doc and code, there are three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If trivial (typo, renamed item): fix doc in the turn.&lt;/li&gt;
&lt;li&gt;If structural: fix what I can and flag the rest as an item in &lt;code&gt;next-session.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If a &lt;code&gt;.exclude/&lt;/code&gt; file becomes obsolete: &lt;strong&gt;delete it or have it clean up discards and completed work&lt;/strong&gt;. There's no "history" file. Memory is a working cache, not a log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AutoDream converts "yesterday" into "2026-03-15." My system does something equivalent but distributed: &lt;code&gt;.exclude/agents/INDEX.md&lt;/code&gt; marks each area with a verification date (&lt;code&gt;stable (2026-05-05)&lt;/code&gt;). When a section is re-confirmed against the code, the date updates. When a date is months old, the agent knows to re-read that area before trusting it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit evidence:&lt;/strong&gt; Claude's memory has no relative dates; it already uses absolute ones (&lt;code&gt;2026-05-02&lt;/code&gt;, &lt;code&gt;2026-05-03&lt;/code&gt;). That's good. But it also has no "stability date" mechanism like my &lt;code&gt;INDEX.md&lt;/code&gt;. If &lt;code&gt;bobinitializer_static_ssr.md&lt;/code&gt; becomes obsolete because I change the framework, Claude has no convention to mark it as &lt;code&gt;drift&lt;/code&gt;. My system does: the file would move to &lt;code&gt;drift&lt;/code&gt; in &lt;code&gt;INDEX.md&lt;/code&gt; and the agent would revalidate it in the next session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Prune and Index
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AutoDream:&lt;/strong&gt; Keep &lt;code&gt;MEMORY.md&lt;/code&gt; under 200 lines, remove pointers to dead files, reorder by relevance, resolve contradictions between index and content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My system:&lt;/strong&gt; This is where my protocol is more structured than AutoDream. There's not a single index with one cap; there are &lt;strong&gt;cascading structural caps&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Soft Cap&lt;/th&gt;
&lt;th&gt;Action When Exceeded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; (root)&lt;/td&gt;
&lt;td&gt;350 lines&lt;/td&gt;
&lt;td&gt;Move section to &lt;code&gt;.exclude/agents/&amp;lt;area&amp;gt;.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;250 lines&lt;/td&gt;
&lt;td&gt;Move heuristics to &lt;code&gt;.exclude/agents/conventions.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.exclude/agents/&amp;lt;area&amp;gt;.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;500 lines&lt;/td&gt;
&lt;td&gt;Subdivide by sub-area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.exclude/agents/INDEX.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100 lines&lt;/td&gt;
&lt;td&gt;Re-organize table by domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.exclude/plans/*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;200 lines&lt;/td&gt;
&lt;td&gt;Split into sub-plans&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When the root &lt;code&gt;AGENTS.md&lt;/code&gt; grows beyond 150 lines in a section, that section expands into &lt;code&gt;.exclude/agents/&amp;lt;area&amp;gt;.md&lt;/code&gt; and leaves a ≤20-line summary + pointer in root. The index (&lt;code&gt;INDEX.md&lt;/code&gt;) is a living map that promotes or demotes material according to its stability.&lt;/p&gt;

&lt;p&gt;AutoDream deletes &lt;code&gt;random-notes.md&lt;/code&gt;. My system doesn't even allow it to be created: if a note doesn't fit into an existing area, it either becomes an ad-hoc plan with "done" criteria, or it gets discarded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit evidence:&lt;/strong&gt; Claude's &lt;code&gt;MEMORY.md&lt;/code&gt; has 6 lines (excellent), but the pruning pressure is external: I impose it through the protocol. Without &lt;code&gt;WORKFLOW.md&lt;/code&gt; reminding the agent not to create loose files, Claude's memory would have 20 files instead of 6. The structure of Claude's index is an &lt;strong&gt;effect&lt;/strong&gt;, not a &lt;strong&gt;cause&lt;/strong&gt;. The cause is the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where My System Goes Beyond AutoDream
&lt;/h2&gt;

&lt;p&gt;Anthropic's architecture solves the &lt;em&gt;memory decay&lt;/em&gt; problem for a generic agent. My system solves the same problem but adds layers that only make sense when a human and an agent collaborate for months on a real software project.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Deliberate Bilingualism
&lt;/h3&gt;

&lt;p&gt;AutoDream operates in a single language (English). Claude's memory for my project is also in English, even though I prefer Spanish in conversation. My system intentionally separates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;English:&lt;/strong&gt; code, XML docs, root &lt;code&gt;AGENTS.md&lt;/code&gt;, commits, public artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spanish:&lt;/strong&gt; &lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt;, conversation with the user, planning notes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? Because &lt;code&gt;AGENTS.md&lt;/code&gt; is read by the agent at every session, and the agent reasons better in English for technical identifiers. But the &lt;em&gt;protocol&lt;/em&gt; is designed and reviewed by the human, and my working language is Spanish. AutoDream doesn't have this separation because Claude doesn't converse with a human in their native language within the dream cycle. Mine does.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Public / Private Split
&lt;/h3&gt;

&lt;p&gt;AutoDream doesn't distinguish between memory the project needs to publish and memory that's just for the agent. My system has an explicit boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;docs/ARCHITECTURE.md&lt;/code&gt;&lt;/strong&gt;: the public face. Contains the same rules as &lt;code&gt;.exclude/agents/&lt;/code&gt; but without internal references. It's what I read when I need to remember how the build pipeline works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.exclude/agents/&lt;/code&gt;&lt;/strong&gt;: the private face. Includes operational heuristics like "when in doubt, if breaking it isn't caught by CI, it's a soft rule."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation means I can open the repo publicly without filtering my agent interaction protocol. AutoDream, living in &lt;code&gt;~/.claude/&lt;/code&gt;, is inherently private, but it has no mechanism for &lt;em&gt;selective promotion&lt;/em&gt; to the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Modular Exportable Skills
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;.agents/skills/blazorbit-user/&lt;/code&gt; directory is a reusable knowledge package that doesn't belong to a single repo. It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SKILL.md&lt;/code&gt;: the mental model and golden rules.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;references/components.md&lt;/code&gt;, &lt;code&gt;references/variants.md&lt;/code&gt;, &lt;code&gt;references/icons.md&lt;/code&gt;: auto-regenerated catalogs built by reflection over the compiled assembly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a level of abstraction AutoDream doesn't have: the ability to &lt;strong&gt;export a complete knowledge domain&lt;/strong&gt; and reuse it in other contexts. If tomorrow I start a project that consumes BlazOrbit, I don't copy &lt;code&gt;.exclude/agents/&lt;/code&gt;; I activate the &lt;code&gt;blazorbit-user&lt;/code&gt; skill and the agent already knows how to use the library without me explaining anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Deferred Counter with Forced Decision Threshold
&lt;/h3&gt;

&lt;p&gt;AutoDream triggers every 24h + 5 sessions. My system has a more aggressive aging mechanism:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Items with &lt;code&gt;deferred: N&lt;/code&gt; indicate how many sessions they've been deferred; upon reaching 3, a decision is mandatory: (a) promote to top-priority, (b) downgrade to loose ideas, (c) delete."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's no sleep cycle waiting. If something has gone untouched for three sessions, the protocol &lt;em&gt;forces&lt;/em&gt; a decision in the fourth. This prevents the backlog from becoming a graveyard of "maybe someday."&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Branch Policy as Part of Memory
&lt;/h3&gt;

&lt;p&gt;AutoDream is read-only with respect to code during sleep. My system goes further: memory includes the &lt;em&gt;rule of when not to edit&lt;/em&gt;. The protocol requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If on &lt;code&gt;develop&lt;/code&gt;, suggest creating a derived branch before editing.&lt;/li&gt;
&lt;li&gt;If on &lt;code&gt;master&lt;/code&gt; or a tag, &lt;strong&gt;stop and ask&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Short-lived PRs (1–3 days).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architectural memory is useless if the agent applies it on the wrong branch. AutoDream assumes the execution environment is safe; my system doesn't trust that premise.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Four Invariants That Make This Work
&lt;/h2&gt;

&lt;p&gt;AutoDream and my system look different in implementation, but they share the same design invariants. These are what really matter:&lt;/p&gt;

&lt;h3&gt;
  
  
  Invariant 1: The Startup Index Has a Hard Cap
&lt;/h3&gt;

&lt;p&gt;AutoDream: 200 lines for &lt;code&gt;MEMORY.md&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
My system: 350 lines for &lt;code&gt;AGENTS.md&lt;/code&gt;, with migration pressure at 150.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; If the agent reads too much on startup, it consumes context window it needs for real work. A short index forces deep knowledge to be &lt;em&gt;referenced&lt;/em&gt;, not &lt;em&gt;included&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence:&lt;/strong&gt; Claude's &lt;code&gt;MEMORY.md&lt;/code&gt; for my project has exactly 6 lines. It's not Anthropic magic; it's that the system only generates 6 topic files worth remembering, because everything else is in the repo or was discarded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invariant 2: Deep Memory Is Partitioned by Domain
&lt;/h3&gt;

&lt;p&gt;AutoDream: topic files (&lt;code&gt;debugging.md&lt;/code&gt;, &lt;code&gt;api-conventions.md&lt;/code&gt;).&lt;br&gt;&lt;br&gt;
My system: &lt;code&gt;.exclude/agents/&amp;lt;area&amp;gt;.md&lt;/code&gt; (&lt;code&gt;css-architecture.md&lt;/code&gt;, &lt;code&gt;component-lifecycle.md&lt;/code&gt;, &lt;code&gt;testing.md&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; A monolithic memory file forces the agent to load debugging patterns when it just wants to know how the CSS pipeline works. Partitioning allows loading on demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invariant 3: Memory Is a Cache, Not a Log
&lt;/h3&gt;

&lt;p&gt;AutoDream: removes contradicted entries, prunes obsolete ones, deletes &lt;code&gt;random-notes.md&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
My system: deletes obsolete files from &lt;code&gt;.exclude/&lt;/code&gt;, no &lt;code&gt;done/&lt;/code&gt;, no archive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; If you keep everything, the agent wastes time reasoning about facts that are no longer true. Memory must be &lt;em&gt;aggressively destructive&lt;/em&gt;. The value isn't in what you remembered, but in what you decided &lt;strong&gt;not&lt;/strong&gt; to remember.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence:&lt;/strong&gt; Claude's memory has no &lt;code&gt;bob-charts.md&lt;/code&gt;. The plan exists in the repo (&lt;code&gt;.exclude/plans/bob-charts.md&lt;/code&gt;, 237 lines), but since it's a temporary plan rather than a project invariant, Claude didn't memorize it. Correct. If the plan went to &lt;code&gt;memory/&lt;/code&gt;, it would be noise tomorrow once it's implemented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invariant 4: The Final Source of Truth Is the Code, Not the Memory
&lt;/h3&gt;

&lt;p&gt;AutoDream: converts relative dates to absolute ones to avoid temporal confusion.&lt;br&gt;&lt;br&gt;
My system: "Verify before claiming" — the agent must re-read the code before acting on a memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Notes can lie. Comments can be outdated. The only artifact that doesn't lie is the executable code. Memory that isn't grounded in the reality of the repo is hallucination with a timestamp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence:&lt;/strong&gt; &lt;code&gt;startup_protocol.md&lt;/code&gt; in Claude's memory duplicates &lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt;. If Claude acted on &lt;code&gt;startup_protocol.md&lt;/code&gt; without re-reading &lt;code&gt;WORKFLOW.md&lt;/code&gt;, she could be working from a stale version. My protocol prevents this: the agent reads the live file &lt;em&gt;before&lt;/em&gt; the memory cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: AI Memory Isn't Magic, It's Information Discipline
&lt;/h2&gt;

&lt;p&gt;AutoDream is an excellent implementation of a pattern that, until now, every Claude Code user had to build by hand or suffer the consequences. Anthropic has done the work of systematizing it, and that's valuable.&lt;/p&gt;

&lt;p&gt;But what has worked for me over months in BlazOrbit isn't the automation of sleep; it's the &lt;strong&gt;waking protocol&lt;/strong&gt;. It's the decision that memory lives in the repo, not in &lt;code&gt;~/.claude/&lt;/code&gt;. It's the separation between index and depth. It's the rule that doc and code update in the same turn. It's the line cap that forces restructuring before memory becomes noise.&lt;/p&gt;

&lt;p&gt;The audit of Claude's actual memory confirms something I suspected: &lt;strong&gt;my system doesn't replace automatic memory; it feeds and disciplines it.&lt;/strong&gt; Without &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;.exclude/WORKFLOW.md&lt;/code&gt;, Claude's memory would have generated 20 disorganized files. With them, it generated 6 clean files that faithfully mirror the human protocol. But even so, the agent's memory duplicates information already in the repo and falls behind recent plans. Those are the gaps that only a human protocol, reaffirmed session by session, can close.&lt;/p&gt;

&lt;p&gt;If you're building a project with AI agents, don't wait for AutoDream to arrive. You can have an equivalent system today with four markdown files and a couple of golden rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A short index you read at startup.&lt;/li&gt;
&lt;li&gt;Topic files by domain, with size caps.&lt;/li&gt;
&lt;li&gt;A rolling backlog that ages aggressively.&lt;/li&gt;
&lt;li&gt;And above all: &lt;em&gt;memory is reviewed against the code, never the other way around.&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rest is implementation details.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Memory system and continuous session audit for BlazOrbit. Audited and written by Kimi K2.6 as technical comparison with AutoDream. Reviewed and corrected by the author.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>architecture</category>
    </item>
    <item>
      <title>5 Lessons learned building a Blazor component library</title>
      <dc:creator>BlazOrbit</dc:creator>
      <pubDate>Sun, 03 May 2026 06:40:38 +0000</pubDate>
      <link>https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho</link>
      <guid>https://dev.to/blazorbit/lessons-learned-building-a-blazor-component-library-eho</guid>
      <description>&lt;p&gt;I spent the last few months building &lt;strong&gt;BlazOrbit&lt;/strong&gt;, a component library for Blazor. It's not the first of its kind —MudBlazor, Radzen and Blazorise already exist— so I had to answer a hard question from the start: &lt;strong&gt;why does this need to exist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer turned out to be a set of architectural decisions I want to share, because each one taught me something about building UI frameworks that I didn't know before I started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: CSS classes don't scale in libraries
&lt;/h2&gt;

&lt;p&gt;The standard pattern in component libraries is class toggling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary btn-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't think this is developer-friendly, and there are several points worth making:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name collisions&lt;/strong&gt;: Your &lt;code&gt;.btn-primary&lt;/code&gt; will clash with Bootstrap, Tailwind, or the consumer's own CSS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combinatorial explosion&lt;/strong&gt;: A button can be small, large, disabled, loading, outlined, filled, errored, full-width, with ripple, without ripple... The number of class combinations grows exponentially.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No override surface&lt;/strong&gt;: If the consumer wants all buttons to be purple, they have to override every modifier combination or use &lt;code&gt;!important&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I experimented with BEM, then utility-first CSS, then CSS-in-JS. None of them solved the problem — or maybe none of them just clicked for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I chose
&lt;/h3&gt;

&lt;p&gt;Using a custom &lt;code&gt;&amp;lt;bob-component&amp;gt;&lt;/code&gt; tag and communicating state through &lt;strong&gt;data attributes&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;bob-component&lt;/span&gt;
  &lt;span class="na"&gt;data-bob-component=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;data-bob-size=&lt;/span&gt;&lt;span class="s"&gt;"large"&lt;/span&gt;
  &lt;span class="na"&gt;data-bob-disabled&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--bob-inline-background: #6200ee;"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/bob-component&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Being aware that engines are more optimized for class-based selection, I analyzed the potential cost [1]. It came out to roughly ~0.1ms for a standard DOM. In exchange:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Readability: It's easier to read &lt;code&gt;[data-bob-size="small"]&lt;/code&gt; in CSS than a tangle of dynamic classes.&lt;/li&gt;
&lt;li&gt;Consistency: Component state (active, disabled, etc.) makes more sense as a data attribute.&lt;/li&gt;
&lt;li&gt;Decoupling: Logic from styles. Which might sound contradictory if you think about it, since data attributes do affect styles. The point is that the attribute marks a dynamic state, while the class identifies/defines the design group.&lt;/li&gt;
&lt;li&gt;Testing: Easier automated testing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Along the same chain of decisions, there's the question of how to standardize CSS files.&lt;br&gt;
In the end, the most practical approach was having the global CSS bundle select on &lt;code&gt;[data-bob-component="button"]&lt;/code&gt; and &lt;code&gt;[data-bob-size="large"]&lt;/code&gt;. And the scoped component CSS declares private variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bob-component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"button"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_button-background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bob-inline-background&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--palette-primary&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bob-component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"button"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_button-background&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives consumers three override levels that compose cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instance&lt;/strong&gt;: pass the &lt;code&gt;BackgroundColor&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme&lt;/strong&gt;: redefine &lt;code&gt;--palette-primary&lt;/code&gt; in their CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global CSS&lt;/strong&gt;: target &lt;code&gt;[data-bob-component="button"]&lt;/code&gt; directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No &lt;code&gt;!important&lt;/code&gt;. No specificity wars. No class leakage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: Reflection is fast if you only do it once
&lt;/h2&gt;

&lt;p&gt;After deciding to group components based on &lt;code&gt;IHas*&lt;/code&gt; interfaces (IHasSize, IHasBorder, IHasPrefix...), which let me unify component rendering and standardize their implementation, I got worried about performance. Each component render needs to know which &lt;code&gt;IHas*&lt;/code&gt; interfaces it implements in order to emit the right attributes. Doing reflection per render would be catastrophic.&lt;/p&gt;

&lt;p&gt;The solution was a &lt;code&gt;[Flags]&lt;/code&gt; enum cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ComponentFeatures&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Variant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Density&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1u&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ... 22 flags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first encounter with a component type, we inspect its interfaces and store the bitmask in a &lt;code&gt;ConcurrentDictionary&amp;lt;Type, TypeInfo&amp;gt;&lt;/code&gt;. Subsequent renders of the same component type read the cached flags. The expensive path runs &lt;strong&gt;once per type, per process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And I went a bit further. Not all attributes change at the same frequency. &lt;code&gt;Size&lt;/code&gt; and &lt;code&gt;Density&lt;/code&gt; change when the parent reconfigures the component. &lt;code&gt;Loading&lt;/code&gt;, &lt;code&gt;Disabled&lt;/code&gt;, and &lt;code&gt;Error&lt;/code&gt; toggle on every user interaction. Rebuilding the full attribute set on every &lt;code&gt;StateHasChanged&lt;/code&gt; would be wasteful.&lt;/p&gt;

&lt;p&gt;So I split the pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BuildStyles&lt;/strong&gt; (runs on &lt;code&gt;OnParametersSet&lt;/code&gt;): full rebuild, reflection cache hit, all attributes computed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PatchVolatileAttributes&lt;/strong&gt; (runs on &lt;code&gt;BuildRenderTree&lt;/code&gt;): only rewrites the 7 high-frequency state attributes. No dictionary allocation. No reflection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In DEBUG builds we instrument the pipeline with &lt;code&gt;IBOBPerformanceService&lt;/code&gt;. On a regular PC, &lt;code&gt;PatchVolatileAttributes&lt;/code&gt; costs ~0.02ms per component. Fast enough not to worry about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: A lot of customization is not enough customization
&lt;/h2&gt;

&lt;p&gt;I obviously put a lot of effort into making things customizable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable exposure&lt;/li&gt;
&lt;li&gt;Theme creation. Dark and light mode...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even so, I decided (again) to go to war and design an extensible variant system.&lt;/p&gt;

&lt;p&gt;In most libraries, a variant is an &lt;code&gt;if&lt;/code&gt; branch inside the &lt;code&gt;.razor&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if (Variant == "primary")
{
    &amp;lt;button class="btn-primary"&amp;gt;...&amp;lt;/button&amp;gt;
}
else if (Variant == "secondary")
{
    &amp;lt;button class="btn-secondary"&amp;gt;...&amp;lt;/button&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or it just modifies a CSS class.&lt;/p&gt;

&lt;p&gt;This is simple and obvious. It's also fundamentally closed. If the consumer wants a &lt;code&gt;Gradient&lt;/code&gt; variant, they have to fork the library or wrap the component.&lt;/p&gt;

&lt;p&gt;I tried several approaches to open it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IIncrementalGenerator&lt;/strong&gt;: Which generated variants based on an enum. This implementation was actually in the first component library I built, which was really more of a prototype for the current BlazOrbit. It worked well, but it was a nightmare to extend due to the sheer amount of generated code. Plus it hid how everything worked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inheritance&lt;/strong&gt;: Doesn't fit with defining Variant as a component attribute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I came up with something I'm still not entirely sure is brilliant, a fever dream, or a monumental blunder — but the truth is that in BlazOrbit you can use a &lt;strong&gt;registry pattern&lt;/strong&gt; to define component variants. Just like registering a service.&lt;br&gt;
&lt;code&gt;Func&amp;lt;TComponent, RenderFragment&amp;gt;&lt;/code&gt; delegates are stored in a singleton dictionary, keyed by &lt;code&gt;(ComponentType, VariantType, Name)&lt;/code&gt;. The component core then retrieves them.&lt;/p&gt;

&lt;p&gt;It looks like this using static RenderFragments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;BOBButtonVariant&lt;/span&gt; &lt;span class="n"&gt;customVariant1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BOBButtonVariant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Custom1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBlazOrbitVariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BOBButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddVariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customVariant2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MyTemplates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetButtonTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or by inheritance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;BOBButtonVariant&lt;/span&gt; &lt;span class="n"&gt;customVariant2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BOBButtonVariant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Custom2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBlazOrbitVariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BOBButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddVariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customVariant2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyButtonTemplate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CloseComponent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variant receives the live component instance. It can access &lt;code&gt;ComputedAttributes&lt;/code&gt;, parameters and state. It can restructure the DOM entirely. And it lives in &lt;strong&gt;the consumer's&lt;/strong&gt; code, not the library's.&lt;/p&gt;

&lt;p&gt;The trade-off is that the component's &lt;code&gt;.razor&lt;/code&gt; file becomes a thin dispatcher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if (VariantRegistry.GetTemplate(GetType(), Variant, this) is RenderFragment template)
{
    @template
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first this felt strange —the component doesn't own its own markup— but it unlocks something no class-based component library offers: &lt;strong&gt;open variants&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm not entirely sure whether it's viable in static SSR contexts — if anyone wants to try... :)&lt;br&gt;
But hey, it's out there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lesson 4: The build pipeline and its dependencies must not leak to consumers
&lt;/h2&gt;

&lt;p&gt;BlazOrbit's CSS bundle is generated by a custom build tool I created ad-hoc: &lt;a href="https://www.nuget.org/packages/CdCSharp.BuildTools" rel="noopener noreferrer"&gt;&lt;code&gt;CdCSharp.BuildTools&lt;/code&gt;&lt;/a&gt;. To be honest it's somewhat tightly coupled to how BlazOrbit uses it, but hey — it's open source and it's out there. It's attribute-based and lets you generate files at build time and run Node sequences (npm install, tsc, vite), all driven by what it generates itself.&lt;/p&gt;

&lt;p&gt;With that I transpile TypeScript modules (I didn't want to give it up), generate the bundles. And everything cleans up when you Clean the project.&lt;/p&gt;

&lt;p&gt;It's transparent to the consumer — no delegation to Node, npm, vite or any equivalent. And at the same time it offers all those capabilities during development.&lt;/p&gt;

&lt;p&gt;I split the pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintainer build&lt;/strong&gt; (&lt;code&gt;BlazOrbit.Dev.targets&lt;/code&gt;): runs BuildTools, regenerates assets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumer build&lt;/strong&gt; (&lt;code&gt;BlazOrbit.targets&lt;/code&gt; inside the &lt;code&gt;.nupkg&lt;/code&gt;): does nothing. The pre-packaged CSS and JS are distributed as &lt;code&gt;staticwebassets&lt;/code&gt; inside the package. The Razor SDK serves them automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Lesson 5: Multi-targeting is not free
&lt;/h2&gt;

&lt;p&gt;Supporting .NET 8 and .NET 10 from the same source code sounds simple: just add &lt;code&gt;&amp;lt;TargetFrameworks&amp;gt;net8.0;net10.0&amp;lt;/TargetFrameworks&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Reality is messier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Razor compiler behaves differently across versions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.AspNetCore.Components.Web&lt;/code&gt; has breaking changes in parameter validation.&lt;/li&gt;
&lt;li&gt;Build tools run on &lt;code&gt;net10.0&lt;/code&gt; but must generate assets that work on both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst bug I hit was a race condition in multi-targeting builds. The &lt;code&gt;BeforeBuild&lt;/code&gt; target that invokes BuildTools was running in both the outer build and the inner ones simultaneously, causing file lock conflicts on &lt;code&gt;package.json&lt;/code&gt;. The fix was a condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;Condition="'$(IsCrossTargetingBuild)' == 'true' OR '$(TargetFrameworks)' == ''"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures BuildTools runs &lt;strong&gt;once per outer build&lt;/strong&gt;, not once per TFM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we are now
&lt;/h2&gt;

&lt;p&gt;BlazOrbit is at &lt;code&gt;1.0.0-preview.N&lt;/code&gt;. The architecture is stable. The API surface almost too. But still open to changes for now. I'm collecting feedback, adjusting things, and adding a few things to the MVP.&lt;br&gt;
Also waiting for contributors (ahem...) — if any brave soul with a taste for code feels like it, you know where to find us.&lt;/p&gt;

&lt;p&gt;And if you're building a component library, I hope these lessons save you a few weeks of experimentation. If you're a Blazor developer, that is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;BlazOrbit is open source (MIT). Issues and discussions are open. Any kind of feedback is welcome.&lt;/em&gt;&lt;br&gt;
Give it a try and let me know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blazorbit.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Website&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BlazOrbit/BlazOrbit" rel="noopener noreferrer"&gt;&lt;strong&gt;Source code&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/BlazOrbit/" rel="noopener noreferrer"&gt;&lt;strong&gt;NuGet package&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/P2kNnT4E" rel="noopener noreferrer"&gt;&lt;strong&gt;Discord Community&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Other topics I may cover in a future post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS standards audit tests across three domains: DOM-emitted, CSS-declared, and library constants&lt;/li&gt;
&lt;li&gt;Integration vs. unit tests for component libraries. Spoiler: Integration testing across two scenarios: Server and Wasm&lt;/li&gt;
&lt;li&gt;Extending some native Blazor components vs. full customization&lt;/li&gt;
&lt;li&gt;Decision: The official documentation resides in the source code.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>architecture</category>
      <category>dotnet</category>
      <category>blazor</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
