<?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: HomelessCoder</title>
    <description>The latest articles on DEV Community by HomelessCoder (@homeless-coder).</description>
    <link>https://dev.to/homeless-coder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3522575%2F17fb8c99-8006-422b-91c7-788821632278.png</url>
      <title>DEV Community: HomelessCoder</title>
      <link>https://dev.to/homeless-coder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/homeless-coder"/>
    <language>en</language>
    <item>
      <title>AI Tool-Calling: Translating Business Requirements to Database Schemas</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Sun, 12 Jul 2026 19:51:07 +0000</pubDate>
      <link>https://dev.to/homeless-coder/ai-tool-calling-translating-business-requirements-to-database-schemas-m66</link>
      <guid>https://dev.to/homeless-coder/ai-tool-calling-translating-business-requirements-to-database-schemas-m66</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 2 of the Omnismith "Core Platform Capabilities" series&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Integrated AI assistants can map natural language schema requirements to operational APIs, deploying complete database schemas and demo datasets in real time. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The initial setup of an entity database with integrated telemetry requires substantial manual configuration. Developers building solutions for clients and technical operators bootstrapping operations must define schema structures, configure API endpoints, and establish relational integrity between physical assets and time-series logs before a single metric can be ingested. This initialization phase delays functional deployment and increases setup friction.&lt;/p&gt;

&lt;p&gt;Omnismith addresses this setup delay by exposing its backend OpenAPI specification directly to an integrated AI Assistant. This architecture allows the assistant to interpret structured schema requirements written in natural language, map them to operational API endpoints, and deploy completed schemas on a blank project in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-Assisted Cold-Chain Fleet Management Schema
&lt;/h2&gt;

&lt;p&gt;To bypass this manual boilerplate, a user can submit a single, plain-text request directly to the Omnismith AI Assistant interface:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a fleet management schema with Vehicles, Shipments, and Drivers. Link shipments to vehicles and drivers, and track temperature and fuel metrics on vehicles."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI Assistant directly parses this requirement, coordinates the dependent operations, and executes the necessary API calls in sequence against the live platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezmko1vy7n7gcy16d7s5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezmko1vy7n7gcy16d7s5.png" alt="Omnismith, AI Assistant, Scaffolding Schemas Using Natural Language" width="800" height="781"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Automated Model Scaffolding
&lt;/h2&gt;

&lt;p&gt;Upon receiving the prompt, the assistant acts as a high-level database orchestrator. It immediately identifies the three primary entities: &lt;strong&gt;Vehicles&lt;/strong&gt;, &lt;strong&gt;Drivers&lt;/strong&gt;, and &lt;strong&gt;Shipments&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To minimize network overhead, the assistant calls a consolidated system orchestration tool, &lt;code&gt;scaffold_data_model&lt;/code&gt;, to register the core templates and their initial properties inside the dynamic Entity-Attribute-Value (EAV) database:&lt;/p&gt;

&lt;h3&gt;
  
  
  Vehicle Generation
&lt;/h3&gt;

&lt;p&gt;The assistant identifies the requirement to track identity, model, fuel, and temperature. It packages these into the first scaffold payload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool&lt;/strong&gt;: &lt;code&gt;scaffold_data_model&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"templateName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vehicle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vehicle ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Make/Model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fuel Level"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Temperature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Driver Generation
&lt;/h3&gt;

&lt;p&gt;The assistant scaffolds the profile template required to represent staff records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool&lt;/strong&gt;: &lt;code&gt;scaffold_data_model&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"templateName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Driver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Driver Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"License Number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shipment Generation
&lt;/h3&gt;

&lt;p&gt;The assistant maps out the shipment record structure, registering placeholders for relational references pointing to the driver and vehicle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool&lt;/strong&gt;: &lt;code&gt;scaffold_data_model&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"templateName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shipment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shipment ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Destination"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Assigned Vehicle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Assigned Driver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 2: Closed-Loop Validation and Refinement
&lt;/h3&gt;

&lt;p&gt;Omnismith utilizes a closed-loop validation engine. To verify complete execution, the assistant reads the newly deployed schema back from the platform and evaluates it against the logical requirements.&lt;/p&gt;

&lt;p&gt;This verification pass ensures structural and temporal properties are precisely configured before telemetry ingestion begins. The assistant executes a target refinement step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry Metric Promotion&lt;/strong&gt;: The initial scaffolding pass registered the &lt;code&gt;Fuel Level&lt;/code&gt; and &lt;code&gt;Temperature&lt;/code&gt; fields as baseline numerical attributes. To support high-frequency time-series chart streaming, the validation loop promotes both fields to telemetry-optimized metric fields (&lt;code&gt;data_type: 1&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Establishing Referential Integrity
&lt;/h2&gt;

&lt;p&gt;With clean template shapes in place, the assistant completes the schema by weaving the logical connections between the templates.&lt;/p&gt;

&lt;p&gt;It maps the dynamic reference parameters on the &lt;code&gt;Shipment&lt;/code&gt; template, instructing the relational system to look up concrete, real-time records from the &lt;code&gt;Vehicle&lt;/code&gt; and &lt;code&gt;Driver&lt;/code&gt; registries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vehicle Link&lt;/strong&gt;: The assistant maps the &lt;code&gt;Assigned Vehicle&lt;/code&gt; reference attribute directly to the display attribute of the target &lt;code&gt;Vehicle&lt;/code&gt; template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Driver Link&lt;/strong&gt;: The assistant repeats the relational mapping for the operator records.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the Omnismith database engine runs on a fully dynamic runtime schema, these alterations execute instantly with no physical database migration files, zero downtime, and zero manual endpoint compilation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Structural and Behavioral Tradeoffs
&lt;/h2&gt;

&lt;p&gt;Conversational schema scaffolding trades absolute, deterministic predictability for rapid development speed.&lt;/p&gt;

&lt;p&gt;When developers write declarative ORM schemas, migration scripts, or JSON configuration models, the resulting environment matches the written code exactly. When relying on an LLM-driven assistant, overly descriptive instructions or grammatical nuances can sometimes require structural verification passes.&lt;/p&gt;

&lt;p&gt;To offset these behavioral risks, Omnismith relies on two structural guardrails:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Closed-Loop Verification&lt;/strong&gt;: The agent continuously diffs the deployed API state against the intended design model, using targeted calls to refine structural details on the fly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Verification Layer&lt;/strong&gt;: The resulting schema displays immediately in the web application's &lt;strong&gt;Template Library&lt;/strong&gt; and &lt;strong&gt;Attribute Library&lt;/strong&gt;. This allows developers to manually tune specific relationship constraints, tweak validation parameters, or tighten boundary conditions before ingesting operational telemetry.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Populating the Schema: Conversational Entity Ingestion
&lt;/h2&gt;

&lt;p&gt;With the database structure actively running, the AI Assistant can manage concrete records within the newly established dynamic templates. Operators can query, analyze, and seed the environment.&lt;/p&gt;

&lt;p&gt;To prepare the environment for operational testing, a user can submit a direct request to populate the fleet system:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Generate realistic demo data for this project. Requirements: minimum 5 drivers, 7 vehicles, and 4 shipments. Ensure the dataset reflects diverse business scenarios and edge cases"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because shipments contain strict relational constraints—requiring references to physical vehicles and registered drivers—the AI Assistant cannot execute these creations as a single batch. It coordinates a logical multi-step deployment strategy shown in the interface trace:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Independent Ingestion&lt;/strong&gt;: The assistant first registers the independent primary resources. It calls &lt;code&gt;create_entity&lt;/code&gt; 12 consecutive times to instantiate 5 unique driver profiles and 7 physical fleet vehicles with varied baselines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference Resolution&lt;/strong&gt;: To link the upcoming shipment logs to these exact entries, the assistant executes 2 calls to &lt;code&gt;search_entities&lt;/code&gt;. This returns the dynamic database state, mapping the human-readable driver names and vehicle license IDs to their platform-generated UUIDv7 keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relational Ingestion&lt;/strong&gt;: With the required reference keys resolved, the assistant executes the final 4 &lt;code&gt;create_entity&lt;/code&gt; calls. This populates the &lt;code&gt;Shipment&lt;/code&gt; records, mapping individual waybills to the resolved vehicle and driver identifiers while introducing complex logistics parameters (such as sub-zero frozen cargo targets and baseline fuel levels).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting records populate the system instantly, providing technical operators with a functional sandbox that models actual business environments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8h63mxm31ctdl3jhm4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8h63mxm31ctdl3jhm4k.png" alt="Omnismith, AI Assistant, Creating Records Using Natural Language" width="800" height="659"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Omnismith, AI Assistant, Creating Records Using Natural Language&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwm62vkmhefshwdcqdcq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwm62vkmhefshwdcqdcq.png" alt="Omnismith, Vehicle Template, Entity Search Page" width="800" height="797"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Omnismith, Vehicle Template, Entity Search Page&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fup2dvche4vs97zh1pp1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fup2dvche4vs97zh1pp1v.png" alt="Omnismith, Vehicle Entity Quick Edit Dialog" width="800" height="809"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Omnismith, Vehicle Entity Quick Edit Dialog&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flz856yjux22gh6tpc6iy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flz856yjux22gh6tpc6iy.png" alt="Omnismith, Vehicle Entity Audit Log" width="800" height="638"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Omnismith, Vehicle Entity Audit Log&lt;/em&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Operational Readiness
&lt;/h3&gt;

&lt;p&gt;The finalized schema produces an immediately queryable, typed database structure. Every template and attribute is initialized with a distinct, immutable UUIDv7. The platform is ready to accept ingestion streams and transactional updates immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Ingestion&lt;/strong&gt;: Temperature sensors mounted on fleet assets can start streaming sensor metrics straight to the platform's high-volume metric stream using the endpoint: &lt;code&gt;POST /v1/entities/{entity_id}/metrics&lt;/code&gt;. This bypasses the primary document update path, sending values directly to TimescaleDB for sub-second chart visualization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relational Search Queries&lt;/strong&gt;: The query engine natively resolves the references established by the assistant. Operators can run complex queries to filter all active &lt;code&gt;Shipment&lt;/code&gt; entities associated with a specific vehicle ID where the vehicle state is flagged as active.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Trailing&lt;/strong&gt;: Any change to these attributes is continuously written to an append-only transaction ledger on TimescaleDB, detailing what changed, when, and by whom from the first second of deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cold-chain fleet management model developed in this guide illustrates how natural language requirements map directly to relational schemas and telemetry databases. While built for logistics, these same AI-driven scaffolding and conversational ingestion mechanics apply to any operational domain, from facility management to hardware infrastructure tracking.&lt;/p&gt;

&lt;p&gt;In the next entry of this series, the platform will utilize this active cold-chain database to demonstrate dynamic automations, configure multi-role access controls (RBAC/ABAC), and build real-time monitoring dashboards.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Manual REST Alternative
&lt;/h2&gt;

&lt;p&gt;To understand how the AI Assistant automates schema deployment, it is valuable to look at how a developer constructs this exact environment manually. Under the hood, Omnismith exposes structured REST endpoints to register templates and attributes dynamically at runtime.&lt;/p&gt;

&lt;p&gt;In a traditional development cycle, creating this cold-chain fleet management schema requires sequentially executing a series of dependent API requests.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Creating the Templates
&lt;/h3&gt;

&lt;p&gt;First, the developer must register empty template containers for each operational concept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/v1/templates&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.omnismith.io&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer &amp;lt;token&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vehicle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fleet assets equipped with environmental sensors"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a template identifier (e.g., &lt;code&gt;019f51bf-366c-72b9-94e1-0435af01e34a&lt;/code&gt;), which must be recorded to associate attributes in subsequent steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Registering Attributes
&lt;/h3&gt;

&lt;p&gt;Next, individual attributes must be registered and attached to their templates. For instance, creating the high-frequency environmental telemetry attribute &lt;code&gt;Temperature&lt;/code&gt; requires specifying its metric type and numerical data type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/v1/attributes&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.omnismith.io&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer &amp;lt;token&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Temperature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attribute_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"template_ids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"019f51bf-366c-72b9-94e1-0435af01e34a"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Establishing References
&lt;/h3&gt;

&lt;p&gt;Finally, the developer must configure relational integrity for reference fields—such as linking a shipment to its assigned driver—by updating the reference configuration on the respective attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;PUT&lt;/span&gt; &lt;span class="nn"&gt;/v1/attributes/019f51bf-3a42-71a3-95db-3736a6ad980b/reference&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.omnismith.io&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer &amp;lt;token&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target_template_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"019f51bf-3836-71a9-a8d7-d5365ee60f3d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target_attribute_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"019f51bf-373f-714f-a811-7d983da1206f"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While these endpoints offer complete control, building a full relational schema manually requires tracking numerous UUID dependencies and drafting boilerplate payloads.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>architecture</category>
      <category>api</category>
    </item>
    <item>
      <title>Scaffolding Business Domain Models with Dynamic Schemas</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Sun, 12 Jul 2026 19:40:09 +0000</pubDate>
      <link>https://dev.to/homeless-coder/scaffolding-business-domain-models-with-dynamic-schemas-1ic0</link>
      <guid>https://dev.to/homeless-coder/scaffolding-business-domain-models-with-dynamic-schemas-1ic0</guid>
      <description>&lt;p&gt;&lt;em&gt;This article kicks off our new "Core Platform Capabilities" series&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Operational platforms require structured data, but rigid database schemas slow down development. Omnismith uses a runtime EAV model to let schemas evolve alongside the business.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Operational platforms require structured data to track physical assets, inventory, or internal resources. Traditional relational databases mandate rigid schema definitions. Altering these schemas to accommodate new business requirements introduces migration files, application deployments, and potential database downtime.&lt;/p&gt;

&lt;p&gt;Omnismith resolves this constraint by implementing a dynamic schema architecture based on the Entity-Attribute-Value (EAV) model. This structure permits runtime modifications to the domain model without altering the underlying database tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Foundational Components
&lt;/h2&gt;

&lt;p&gt;The platform divides domain modeling into three foundational components: Attributes, Templates, and Entities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attributes&lt;/strong&gt; function as field definitions. They enforce data types and structural constraints. An attribute can be configured as a dimension (representing structural properties like strings, booleans, or dates), a metric (recording time-series values), a list (providing enumerated options), or a reference (linking to other entities).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates&lt;/strong&gt; act as reusable schemas. A template groups specific attributes to define the shape of a business object. For example, a template representing server infrastructure might group a hostname string attribute, an environment list attribute, and a CPU utilization metric attribute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entities&lt;/strong&gt; are the instantiated records. Every entity belongs to a template and populates the predefined attributes with explicit values.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Querying and Performance Tradeoffs
&lt;/h2&gt;

&lt;p&gt;This architecture introduces a specific tradeoff. Querying an EAV structure requires more complex SQL operations than querying a flat relational table. Omnismith manages this complexity internally, exposing a unified entity search interface that supports filtering, sorting, and pagination without exposing the underlying table structure to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime Schema Evolution
&lt;/h2&gt;

&lt;p&gt;The resulting system allows users to define custom business domains, group properties into templates, and deploy them to active projects instantly. Data structures are modeled and populated entirely at runtime, allowing the data model to evolve alongside the business.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>architecture</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Self-Hosting Prometheus and Grafana for Omnismith (For Now)</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:05:51 +0000</pubDate>
      <link>https://dev.to/homeless-coder/self-hosting-prometheus-and-grafana-for-omnismith-for-now-pf9</link>
      <guid>https://dev.to/homeless-coder/self-hosting-prometheus-and-grafana-for-omnismith-for-now-pf9</guid>
      <description>&lt;p&gt;When building a data-intensive platform, real operational visibility becomes necessary quickly. A core API might be fast, but if event consumers lag or the database strains, the system degrades.&lt;/p&gt;

&lt;p&gt;The industry reflex is often to reach for a managed SaaS provider or immediately start instrumenting the codebase with OpenTelemetry. But for an early-to-mid-stage project, SaaS ingest pricing can rapidly drain a budget, and app-level instrumentation can be a distraction when basic infrastructure baselines do not yet exist.&lt;/p&gt;

&lt;p&gt;A pragmatic, battle-tested standard provides a solution: a self-hosted stack using &lt;strong&gt;Prometheus, Grafana, and dedicated Exporters&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Here is a look at the architecture, the tradeoffs, and why this is the right "Phase 1" approach for scaling platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observing Infrastructure Directly
&lt;/h2&gt;

&lt;p&gt;Before instrumenting application-level spans and traces, a platform first needs a bedrock of infrastructure visibility. By starting with the underlying state and event layers (PostgreSQL and Kafka), a decoupled pipeline for system health is established.&lt;/p&gt;

&lt;p&gt;Omnismith relies on robust foundational infrastructure. This separation of concerns keeps the application fast and reliable, but it introduces operational complexity. If the database experiences heavy query load or if Kafka consumers fall behind, the platform's performance degrades. &lt;/p&gt;

&lt;p&gt;By collecting metrics directly from the infrastructure itself using &lt;code&gt;postgres-exporter&lt;/code&gt; and &lt;code&gt;kafka-exporter&lt;/code&gt;, a baseline of operational health is established that complements future application-level telemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture and Extensibility
&lt;/h2&gt;

&lt;p&gt;By combining Prometheus with dedicated infrastructure exporters, robust telemetry is achieved without adding latency to the primary request path or polluting the application domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------------+                         +------------------+
|    PostgreSQL    |                         |   Apache Kafka   |
|    (Database)    |                         |    (Event Bus)   |
+------------------+                         +------------------+
          |                                            |
          v                                            v
+------------------+                         +------------------+
|     Postgres     |                         |       Kafka      |
|     Exporter     |                         |      Exporter    |
+------------------+                         +------------------+
          |                                            |
          |           +------------------+             |
          +---------&amp;gt; |    Prometheus    | &amp;lt;-----------+
                      | (Scrape &amp;amp; Alert) | 
                      +------------------+
                                |
                                v
                      +------------------+
                      |     Grafana      |
                      |   (Dashboards)   |
                      +------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component fulfills a specific operational boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exporters&lt;/strong&gt;: Run alongside the core infrastructure, exposing internal metrics (e.g., table sizes, partition counts, consumer group lag) over HTTP. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus&lt;/strong&gt;: Periodically scrapes these exporters, maintains time-series data, and evaluates recording rules to trigger alerts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting &amp;amp; Visualization&lt;/strong&gt;: Alerts are routed to communication channels via Grafana Alerting to avoid active dashboard monitoring. Grafana also queries Prometheus to provide unified, real-time visualization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design is highly extensible. As infrastructure complexity grows, additional metrics can be collected by simply provisioning new exporters and adding them as Prometheus scrape targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The TCO Tradeoff: Financial Efficiency vs. Maintenance
&lt;/h2&gt;

&lt;p&gt;In the early and growth phases of a platform, infrastructure decisions require a balance between operational capability and financial discipline.&lt;/p&gt;

&lt;p&gt;Deploying and managing a self-hosted observability stack rather than relying immediately on managed cloud SaaS providers yields two major advantages:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Direct Financial Efficiency
&lt;/h3&gt;

&lt;p&gt;Managed monitoring solutions often carry steep pricing structures linked to ingested data volume. &lt;/p&gt;

&lt;p&gt;While self-hosting requires engineering time to maintain, avoiding the steep data-ingest pricing drastically reduces direct cash burn in the early stages. Accepting the trade-off that self-hosting a stateful time-series database introduces its own maintenance burden (such as managing storage retention) is necessary. However, at current platform volumes, the financial savings vastly outweigh the operational overhead, directly extending runway.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Deep Platform Understanding
&lt;/h3&gt;

&lt;p&gt;Operating complex distributed infrastructure hands-on builds essential domain expertise. &lt;/p&gt;

&lt;p&gt;Configuring Prometheus scrape intervals, understanding exporter metrics, and provisioning Grafana dashboards provides deep visibility into low-level system mechanics. When the platform eventually scales to warrant managed cloud instances (such as AWS Managed Prometheus), the transition can be executed efficiently with an exact understanding of system requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By monitoring the foundational infrastructure directly and operating an efficient self-hosted stack, the platform gains an extensible observability layer and significant cost savings during a critical growth phase. Application instrumentation is definitely next, but this infrastructure baseline had to come first.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>architecture</category>
      <category>infrastructure</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Blueprint Marketplace for Reusable Project Structures</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Tue, 07 Apr 2026 21:07:20 +0000</pubDate>
      <link>https://dev.to/homeless-coder/building-a-blueprint-marketplace-for-reusable-project-structures-5e7l</link>
      <guid>https://dev.to/homeless-coder/building-a-blueprint-marketplace-for-reusable-project-structures-5e7l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Flexible platforms create repeated modeling work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I saw that clearly while building &lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;Omnismith&lt;/a&gt;. Users can model their own business domains with templates, attributes, references, and entities. Many of those domains still start from similar structures.&lt;/p&gt;

&lt;p&gt;Product catalogs, asset tracking, server monitoring, and CRM-style workflows use different labels and fields. They still reuse many of the same structural ideas.&lt;/p&gt;

&lt;p&gt;That observation led to the Blueprint Marketplace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6dlriuaslfltozmp6jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6dlriuaslfltozmp6jq.png" alt=" " width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeated domain work
&lt;/h2&gt;

&lt;p&gt;Flexible platforms give users room to model their own systems. They also push a lot of design work onto each new workspace.&lt;/p&gt;

&lt;p&gt;When the same structures are rebuilt in one workspace after another, that is repeated setup cost.&lt;/p&gt;

&lt;p&gt;It also keeps useful ideas trapped inside isolated projects.&lt;/p&gt;

&lt;p&gt;If one team has already designed a strong starting structure for a product catalog, a monitoring workspace, or a roadmap tracker, the product should make that reusable. Otherwise every new project starts by rediscovering work that was already done.&lt;/p&gt;

&lt;p&gt;Omnismith needed a way to turn repeated domain modeling into reusable project structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Template Marketplace" was not enough
&lt;/h2&gt;

&lt;p&gt;The first internal name for this was Template Marketplace.&lt;/p&gt;

&lt;p&gt;The name was too narrow.&lt;/p&gt;

&lt;p&gt;A template is only one piece of what makes a project usable. A real starting point usually includes several templates, multiple attributes, references between models, and a set of decisions about how those pieces fit together. Publishing templates alone would expose fragments. I needed a way to package and publish a coherent project structure.&lt;/p&gt;

&lt;p&gt;That is why the concept became Blueprint Marketplace.&lt;/p&gt;

&lt;p&gt;The term “blueprint” covered the larger unit more accurately. It described a reusable structure that could be installed into another project and still make sense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmja50icsozk9u3vr5vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmja50icsozk9u3vr5vg.png" alt=" " width="800" height="1261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The publication boundary mattered early
&lt;/h2&gt;

&lt;p&gt;The most important constraint was safety.&lt;/p&gt;

&lt;p&gt;I wanted users to publish reusable structures. I also needed a clear guarantee that the marketplace would not expose actual project records.&lt;/p&gt;

&lt;p&gt;That boundary shaped the model early.&lt;/p&gt;

&lt;p&gt;User-published blueprints carry reusable definitions. They do not carry live business data. Featured blueprints are curated and reviewed before they appear in onboarding or other product-controlled surfaces.&lt;/p&gt;

&lt;p&gt;That distinction made the marketplace usable as both a user-facing feature and an internal product distribution channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The implementation started from an existing example
&lt;/h2&gt;

&lt;p&gt;The Product Catalog dataset had already been written in code with reuse in mind.&lt;/p&gt;

&lt;p&gt;That reduced the amount of conceptual redesign. The real work was everything around it: persistence, publication flow, API support, and UI support.&lt;/p&gt;

&lt;p&gt;I started with console support because it was the fastest way to exercise the publishing model and test the rules around what a blueprint could contain. That gave the rest of the implementation a clearer contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu803elfouov13s4e0efi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu803elfouov13s4e0efi.png" alt=" " width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this changes the product
&lt;/h2&gt;

&lt;p&gt;The Blueprint Marketplace changes how Omnismith can evolve as a platform.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reusable structures can be installed into new projects&lt;/li&gt;
&lt;li&gt;useful domain knowledge becomes portable inside the product&lt;/li&gt;
&lt;li&gt;curated official blueprints can guide important product flows&lt;/li&gt;
&lt;li&gt;onboarding and starter experiences can reuse the same underlying publication system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once reusable blueprints exist as a first-class capability, onboarding does not need to depend on special-case demo logic forever. The same system that supports marketplace publishing can also support official starter projects and curated first-session paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The Blueprint Marketplace started from a simple product observation: flexible platforms still accumulate repeated domain work.&lt;/p&gt;

&lt;p&gt;I did not want Omnismith users to keep rebuilding the same project structures in isolation. I wanted those structures to become publishable, installable, and reusable inside the product itself.&lt;/p&gt;

&lt;p&gt;That made the marketplace worth building on its own. It also created the foundation for the onboarding work that came after it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>startup</category>
      <category>ux</category>
    </item>
    <item>
      <title>Designing Onboarding for a Flexible Data Platform</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Sat, 21 Mar 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/homeless-coder/designing-onboarding-for-a-flexible-data-platform-2j32</link>
      <guid>https://dev.to/homeless-coder/designing-onboarding-for-a-flexible-data-platform-2j32</guid>
      <description>&lt;p&gt;Flexible data products have a predictable onboarding problem. The empty state is technically correct. It also hides the product model.&lt;/p&gt;

&lt;p&gt;That was the issue in Omnismith's original onboarding flow. New users were shown the interface, then dropped into a project with no templates, no attributes, and no entities. The tutorial explained the controls, but it did not show how a real workspace should be structured.&lt;/p&gt;

&lt;p&gt;Users could follow individual actions. They still lacked a working mental model of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blank-canvas problem
&lt;/h2&gt;

&lt;p&gt;Omnismith is flexible by design. Users define templates, attributes, references, and entities to fit their own domain.&lt;/p&gt;

&lt;p&gt;That flexibility creates a cost during the first session. A new user has to understand both the interface and the shape of a finished system.&lt;/p&gt;

&lt;p&gt;The original onboarding handled the first part and missed the second.&lt;/p&gt;

&lt;p&gt;A flexible product is easier to understand when users can inspect a coherent example. Omnismith's onboarding gap came from missing context around the model and the workspace structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the first session needed
&lt;/h2&gt;

&lt;p&gt;The onboarding flow needed to expose the model immediately.&lt;/p&gt;

&lt;p&gt;That meant starting with a project that contained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a concrete schema&lt;/li&gt;
&lt;li&gt;example records&lt;/li&gt;
&lt;li&gt;visible relationships between records&lt;/li&gt;
&lt;li&gt;enough structure to make browsing and editing feel like real work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example also needed to stay small and coherent while still showing how the parts fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first redesign: a seeded demo project
&lt;/h2&gt;

&lt;p&gt;The first redesign used a seeded Product Catalog project.&lt;/p&gt;

&lt;p&gt;The dataset was intentionally small, but structurally complete enough to teach the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 templates: Category, Brand, Product&lt;/li&gt;
&lt;li&gt;11 attributes across those templates&lt;/li&gt;
&lt;li&gt;shared fields such as &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;Description&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;references from Product to Category and Brand&lt;/li&gt;
&lt;li&gt;a self-reference for related products&lt;/li&gt;
&lt;li&gt;19 sample entities with realistic values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was enough to make the system legible. A user could open the project and immediately see that templates define structure, entities hold values, and references connect records into something closer to a real application than an empty workspace does.&lt;/p&gt;

&lt;p&gt;That scope was deliberate. The dataset's job was to make the product model visible within the first few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the guided tour still mattered
&lt;/h2&gt;

&lt;p&gt;The demo project provided context, and the guided tour directed attention.&lt;/p&gt;

&lt;p&gt;Users had a concrete workspace to inspect, and the tour moved them through the important surfaces in the right order.&lt;/p&gt;

&lt;p&gt;Together, they reduced the time between first login and first useful understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this solved, and what it did not
&lt;/h2&gt;

&lt;p&gt;This redesign fixed the immediate blank-canvas failure. New users no longer had to infer the product model from an empty screen.&lt;/p&gt;

&lt;p&gt;It did not solve every onboarding problem.&lt;/p&gt;

&lt;p&gt;One seeded dataset is still one opinionated starting point. It helps users understand the system, but it also assumes that one example should be relevant to everyone. That later became the next onboarding problem to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For flexible products, an empty workspace is a poor first lesson.&lt;/p&gt;

&lt;p&gt;Omnismith became easier to understand after onboarding began with a small working system. Clear visibility into the product model improved first-session comprehension.&lt;/p&gt;

&lt;p&gt;That was the first onboarding redesign. The next question was how to keep that clarity without forcing every user into the same example.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>showdev</category>
      <category>ux</category>
    </item>
    <item>
      <title>Why Omnismith Uses Flexible Schema for Operational Data</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Thu, 19 Mar 2026 07:30:00 +0000</pubDate>
      <link>https://dev.to/homeless-coder/why-omnismith-uses-flexible-schema-for-operational-data-4gg1</link>
      <guid>https://dev.to/homeless-coder/why-omnismith-uses-flexible-schema-for-operational-data-4gg1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Omnismith was built around a simple constraint: many operational systems need different schemas, but the same platform capabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many operational systems have different schemas but the same platform requirements.&lt;/p&gt;

&lt;p&gt;A product catalog needs categories, brands, and products. An infrastructure workspace needs servers, services, incidents, and metrics. An IT asset system needs contracts, devices, licenses, and owners.&lt;/p&gt;

&lt;p&gt;The fields are different. The surrounding platform needs are usually the same.&lt;/p&gt;

&lt;p&gt;You still need structured records, permissions, history, dashboards, imports, API access, and, in some cases, time-series data.&lt;/p&gt;

&lt;p&gt;That is the problem Omnismith was built to address.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Repeated Problem
&lt;/h2&gt;

&lt;p&gt;The core issue is not that teams need databases. The issue is that they repeatedly need systems built around different data models while the surrounding platform requirements stay mostly stable.&lt;/p&gt;

&lt;p&gt;The implementation pattern is familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define a schema&lt;/li&gt;
&lt;li&gt;build CRUD screens&lt;/li&gt;
&lt;li&gt;add permissions&lt;/li&gt;
&lt;li&gt;add history&lt;/li&gt;
&lt;li&gt;add imports and exports&lt;/li&gt;
&lt;li&gt;add charts or reporting&lt;/li&gt;
&lt;li&gt;expose the result through an API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That work is manageable when the schema is stable and the system is narrow.&lt;/p&gt;

&lt;p&gt;It becomes expensive when the structure changes often or when the same product needs to support several operational domains. In those cases, too much work goes into rebuilding the platform around the schema instead of working on the domain itself.&lt;/p&gt;

&lt;p&gt;This is common in internal tools, operational backoffice systems, monitoring workspaces, and domain-specific SaaS products. The schema changes with the business model, but the surrounding platform needs remain familiar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Flexible Schema Is The Core Model
&lt;/h2&gt;

&lt;p&gt;Omnismith uses an Entity-Attribute-Value model so the structure itself can be managed as data.&lt;/p&gt;

&lt;p&gt;In practical terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;attributes define fields&lt;/li&gt;
&lt;li&gt;templates define structure&lt;/li&gt;
&lt;li&gt;entities store actual records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That allows a workspace to define its own schema without requiring code changes for every new field or template.&lt;/p&gt;

&lt;p&gt;If a team needs a &lt;code&gt;Server&lt;/code&gt; template with hostname, CPU cores, environment, and owner, that structure can be created directly in the product. If another team needs &lt;code&gt;Supplier&lt;/code&gt;, &lt;code&gt;Contract&lt;/code&gt;, and &lt;code&gt;Renewal Date&lt;/code&gt;, it can define a different model in the same system.&lt;/p&gt;

&lt;p&gt;The point is not schema flexibility for its own sake. The point is to reduce the cost of building and maintaining operational systems whose structure changes over time.&lt;/p&gt;

&lt;p&gt;This changes the development boundary. Instead of treating every schema adjustment as an application change, the product can treat structure as workspace data and handle the surrounding operational behavior in a consistent way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Dynamic Schema Alone Is Not Enough
&lt;/h2&gt;

&lt;p&gt;Flexible schema alone is not enough for the types of systems Omnismith is meant to support.&lt;/p&gt;

&lt;p&gt;Operational data usually needs more than field definition and record editing.&lt;/p&gt;

&lt;p&gt;It also needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;history, so changes are traceable&lt;/li&gt;
&lt;li&gt;role-based access control, so access is scoped properly&lt;/li&gt;
&lt;li&gt;API-first access, so the workspace can be integrated into other systems&lt;/li&gt;
&lt;li&gt;time-series support, so metrics can be attached to entities instead of stored somewhere else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination defines the actual product boundary.&lt;/p&gt;

&lt;p&gt;Omnismith is meant for systems where the schema is flexible, but auditability and operational visibility still matter.&lt;/p&gt;

&lt;p&gt;That is also why the product is opinionated in a few specific directions. Changes are tracked. Access is scoped. API access is a first-class interface rather than an afterthought. Metrics can be stored against the same entities that define the operational model.&lt;/p&gt;

&lt;p&gt;A server, sensor, contract, or product record does not need a separate system just because one part of its state changes over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Use Cases
&lt;/h2&gt;

&lt;p&gt;This model fits several kinds of structured operational work particularly well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IT asset management&lt;/li&gt;
&lt;li&gt;server and service monitoring&lt;/li&gt;
&lt;li&gt;product and catalog management&lt;/li&gt;
&lt;li&gt;consent and compliance tracking&lt;/li&gt;
&lt;li&gt;internal backoffice systems with changing data models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common pattern is not the domain itself. The common pattern is that each use case needs structured records together with operational behavior around those records.&lt;/p&gt;

&lt;p&gt;An IT asset workspace needs devices, owners, contracts, warranties, and licenses. A monitoring workspace needs services, incidents, servers, and metric history. A catalog needs products, categories, pricing, stock state, and references. These are different models, but they benefit from the same platform shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope
&lt;/h2&gt;

&lt;p&gt;Omnismith is not trying to be a universal replacement for every spreadsheet or content tool.&lt;/p&gt;

&lt;p&gt;The intended scope is narrower and more practical: structured operational data that changes over time, needs clear access control, and benefits from being exposed through an API.&lt;/p&gt;

&lt;p&gt;That is why flexible schema is combined with time-series support, full history, dashboards, RBAC, and API-driven workflows. Those capabilities make the schema model useful in day-to-day operation rather than only convenient during setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Omnismith was built for teams that need to model structured operational data without rebuilding the surrounding platform every time the schema changes.&lt;/p&gt;

&lt;p&gt;Flexible schema is the core mechanism. History, permissions, API access, and time-series support are what make that mechanism useful in practice.&lt;/p&gt;

&lt;p&gt;That is the product boundary: not only storing custom fields, but supporting the operational systems built on top of them.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>showdev</category>
      <category>software</category>
    </item>
    <item>
      <title>Building ng-beacon: a lightweight guided tour library for Angular</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Wed, 11 Mar 2026 08:17:00 +0000</pubDate>
      <link>https://dev.to/homeless-coder/building-ng-beacon-a-lightweight-guided-tour-library-for-angular-3j8i</link>
      <guid>https://dev.to/homeless-coder/building-ng-beacon-a-lightweight-guided-tour-library-for-angular-3j8i</guid>
      <description>&lt;p&gt;I was working on &lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;Omnismith&lt;/a&gt; and needed a decent product tour. That sounded like a solved problem, so I went looking for an Angular package I could install and forget about.&lt;/p&gt;

&lt;p&gt;There were usable options. Some were solid. But none fit the shape I wanted for this app: no manual scripts or stylesheets, no generic abstraction I had to translate back into Angular, and nothing that felt awkward in a &lt;strong&gt;zoneless Signals-based&lt;/strong&gt; app.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

&lt;p&gt;It's called &lt;a href="https://www.npmjs.com/package/ng-beacon" rel="noopener noreferrer"&gt;&lt;code&gt;ng-beacon&lt;/code&gt;&lt;/a&gt;: a lightweight guided tour library for Angular 19+, built around Signals, SVG spotlight overlays, and a deliberately small setup.&lt;/p&gt;

&lt;p&gt;This post is less about “I published a package” and more about the constraints that shaped it, which parts turned out to matter, and how the API changed once it had to survive real application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint Was Simplicity
&lt;/h2&gt;

&lt;p&gt;I didn't want a tour system that became its own mini-framework. I wanted something that looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;provideBeacon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STEPS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the baseline: no Angular Material dependency, no CDK requirement, no big configuration surface, no assumptions about which translation library a consumer uses, and no architectural gravity that would make the feature feel more expensive than it should.&lt;/p&gt;

&lt;p&gt;There are good generic tour libraries. I just didn't want to adapt a generic engine into Angular-shaped application code if I could avoid it. That constraint ended up shaping almost every decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Parts That Were Actually Fun to Build
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HomelessCoder/ng-beacon/raw/main/assets/output.gif" rel="noopener noreferrer"&gt;https://github.com/HomelessCoder/ng-beacon | Demo GIF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started with an SVG spotlight overlay because it gave me clean geometry and smooth control over the cutout. That part worked early. The more interesting problems were everything around it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Click blocking around the spotlight
&lt;/h3&gt;

&lt;p&gt;The SVG handled the visual spotlight nicely, but I still wanted the interaction model to be explicit.&lt;/p&gt;

&lt;p&gt;The highlighted area may need to stay interactive while the rest of the screen still behaves like an overlay. I could have pushed all of that into the SVG layer, but I preferred to separate the visual layer from the interaction layer.&lt;/p&gt;

&lt;p&gt;So I used dynamic click-blocking &lt;code&gt;div&lt;/code&gt;s around the spotlight rectangle. Instead of one full-screen blocker doing everything, the page gets four blocker regions around the highlighted area.&lt;/p&gt;

&lt;p&gt;It is not the only way to solve it, but it made pointer behavior much more explicit and predictable.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tooltip positioning that reacts to real size
&lt;/h3&gt;

&lt;p&gt;The tooltip height changes from step to step. Position changes too. Which means you can't just hardcode offsets and hope for the best.&lt;/p&gt;

&lt;p&gt;So the positioning logic measures the tooltip, calculates where it wants to be, and then clamps it to the viewport edges. That sounds small on paper, but it is the difference between “works in the demo” and “feels stable while resizing the window and moving between steps.”&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Iterating on the motion until it stopped feeling mechanical
&lt;/h3&gt;

&lt;p&gt;I also spent time on the transitions. Nothing dramatic. Just enough to make the spotlight, blockers, and tooltip move like they belong to the same system instead of three separate UI layers reacting independently.&lt;/p&gt;

&lt;p&gt;That polish mattered more than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First API Was Too Simple
&lt;/h2&gt;

&lt;p&gt;My first API was basically this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BeaconStep&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for simple cases, that is still fine. But real application code pushed it pretty quickly. Some tour steps only make sense while a specific component exists, so the library needed a way for components to contribute their own steps instead of keeping everything in one central list.&lt;/p&gt;

&lt;p&gt;That led to &lt;code&gt;registerTourSteps()&lt;/code&gt; and, from there, to &lt;code&gt;startContextTour()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;_tour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;registerTourSteps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DASHBOARD_STEPS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startContextTour&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point the API made more sense. Components owned their local tour context, and the running tour could stay aligned with the actual UI. If a component disappeared during navigation, its steps disappeared with it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;start(steps)&lt;/code&gt; stayed as the simple option. &lt;code&gt;startContextTour()&lt;/code&gt; became the better option for tours that need to follow real application state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internationalization Without Coupling
&lt;/h2&gt;

&lt;p&gt;Omnismith supports eight languages, so I knew very early that hardcoded English strings inside the tour UI were not going to survive long.&lt;/p&gt;

&lt;p&gt;At the same time, I didn't want the library coupled to a specific translation library.&lt;/p&gt;

&lt;p&gt;In Omnismith I use &lt;code&gt;ngx-translate&lt;/code&gt;. Someone else might use Transloco. Someone else might want their own function. So instead of picking a winner, I exposed a helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;provideBeaconTranslateFn&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TranslateService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;That handles both step content and built-in UI labels like close / next / previous. Consumers get flexibility without the package forcing one i18n stack on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests Helped Me Stop Guessing
&lt;/h2&gt;

&lt;p&gt;I added tests with AI assistance. What mattered was covering the behavior that was easy to break while iterating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;step filtering&lt;/li&gt;
&lt;li&gt;navigation behavior&lt;/li&gt;
&lt;li&gt;focus movement and restoration&lt;/li&gt;
&lt;li&gt;overlay rendering&lt;/li&gt;
&lt;li&gt;tooltip positioning branches&lt;/li&gt;
&lt;li&gt;label translation and configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those were in place, I could keep refining the code without mentally re-running the same manual checks every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Final Result Looks Like
&lt;/h2&gt;

&lt;p&gt;The setup stayed small, which was the whole point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideBeacon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng-beacon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;provideBeacon&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (beaconService.isActive()) {
  &lt;span class="nt"&gt;&amp;lt;beacon-overlay&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Direct tour&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MY_TOUR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Component-scoped tour&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startContextTour&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you need the more structured path, that is there too with &lt;code&gt;registerTourSteps&lt;/code&gt;, &lt;code&gt;startContextTour&lt;/code&gt;, and &lt;code&gt;provideBeaconTranslateFn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It also has two small lifecycle events now, &lt;code&gt;finished&lt;/code&gt; and &lt;code&gt;dismissed&lt;/code&gt;, which turned out to be useful for simple tracking and analytics.&lt;/p&gt;

&lt;p&gt;That mattered to me more than I expected. I didn't just want a tour overlay. I wanted something that fit naturally into Angular application code, including translation, component ownership, reactive lifecycle handling, and app-level integration points.&lt;/p&gt;

&lt;p&gt;That was the real target from the start: &lt;strong&gt;simple by default, but not boxed in&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Things I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Small libraries need stronger constraints, not fewer.&lt;/strong&gt; If you don't decide what to exclude, they become messy surprisingly fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API you start with is rarely the API you actually need.&lt;/strong&gt; &lt;code&gt;start(BeaconStep[])&lt;/code&gt; was fine until the app got bigger, tour context became local, and components started destroying themselves mid-tour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework-agnostic integration points are usually worth it.&lt;/strong&gt; The translation hook is a good example. Coupling would have been easier in the short term and worse for everyone else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Polish matters more than feature count for UI infrastructure.&lt;/strong&gt; Smooth motion, stable positioning, and clean focus behavior are what make a tour feel trustworthy.&lt;/p&gt;

&lt;p&gt;If you're building Angular apps and want something that feels more native to Angular application structure than a generic wrapper, maybe this is useful.&lt;/p&gt;

&lt;p&gt;This is my first public TypeScript / Angular library, which makes it more significant to me than a normal internal extraction. Not because it's a giant technical achievement. Just because shipping a public package feels different. It's one thing to write code for your own app. It's another to write something other people might install and judge in five minutes.&lt;/p&gt;

&lt;p&gt;If nothing else, building it reminded me that sometimes the right answer to “surely a package already exists for this” is “yes, but not one I actually want to use.”&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/ng-beacon" rel="noopener noreferrer"&gt;ng-beacon&lt;/a&gt;&lt;br&gt;
👉 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/HomelessCoder/ng-beacon" rel="noopener noreferrer"&gt;HomelessCoder/ng-beacon&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it and something feels awkward, I'd genuinely rather hear that than a polite “looks nice.” That's the only way these packages get better.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>angular</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From an Unemployed Side-Project to AWS Community Builder: Why you should start sharing today</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Wed, 04 Mar 2026 10:23:36 +0000</pubDate>
      <link>https://dev.to/homeless-coder/from-an-unemployed-side-project-to-aws-community-builder-why-you-should-start-sharing-today-4gkf</link>
      <guid>https://dev.to/homeless-coder/from-an-unemployed-side-project-to-aws-community-builder-why-you-should-start-sharing-today-4gkf</guid>
      <description>&lt;p&gt;Six months ago I was unemployed. I was also nervous, uncertain, and had never "built in public" in my life.&lt;/p&gt;

&lt;p&gt;Yesterday, I received an email confirming that I've been accepted into the &lt;strong&gt;AWS Community Builders&lt;/strong&gt; program for 2026-2027.&lt;/p&gt;

&lt;p&gt;I'm writing this because I want to talk to the version of myself from six months ago - the one who was (and still is!) hesitant to hit "Publish" on that first post.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it started
&lt;/h2&gt;

&lt;p&gt;When I found myself between jobs, I decided to pour my energy into an open-source project: &lt;strong&gt;a PHP modular framework&lt;/strong&gt;. I had a specific vision for modular monolith architecture and willingness to share it with the PHP community. I started writing here on dev.to to document the "why" and the "how": &lt;a href="https://dev.to/homeless-coder/series/33542"&gt;Building a Modular PHP Paradigm Series' Articles&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the time, I didn't think of myself as a "writer" or a "content creator". I was just a dev with an idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point
&lt;/h2&gt;

&lt;p&gt;Sharing my work was not just about the code. It was about building confidence, engaging with the community, and developing the idea with insights that were not on my radar before. It led to a very unexpected, yet rewarding outcome: my acceptance into the AWS Community Builders program.&lt;/p&gt;

&lt;h2&gt;
  
  
  My message to you
&lt;/h2&gt;

&lt;p&gt;If you are working on something, even if it feels niche, even if you think "no one will care": Share it anyway.&lt;/p&gt;

&lt;p&gt;You don't just build software when you build in public; you are developing yourself. You build a reputation, a network, and opportunities that you can't even see yet.&lt;/p&gt;

&lt;p&gt;I'm so proud to join this cohort, and I'm incredibly grateful to the dev.to community for being the place where this journey actually began.&lt;/p&gt;

&lt;p&gt;Such an exciting moment!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>buildinpublic</category>
      <category>career</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Launched. Users Showed Up. And Then I Watched Them Get Lost.</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Tue, 24 Feb 2026 07:46:54 +0000</pubDate>
      <link>https://dev.to/homeless-coder/i-launched-users-showed-up-and-then-i-watched-them-get-lost-2d19</link>
      <guid>https://dev.to/homeless-coder/i-launched-users-showed-up-and-then-i-watched-them-get-lost-2d19</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the original founder-side version of the story. From post 3 onward, the series was rewritten in a calmer, more technical style.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first post in this series was about why Omnismith exists. This one is about what happened after I launched it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Parts Nobody Sees on the Commit Graph
&lt;/h2&gt;

&lt;p&gt;I've been a software engineer for a long time. What I had never done was build a complete commercial product from zero — alone — with real money and real legal obligations on the line.&lt;/p&gt;

&lt;p&gt;Some of what that actually involved:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two full architectural iterations.&lt;/strong&gt; The first codebase went in a direction I wasn't satisfied with. I scrapped it and started over with a modular monolith on &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;my own framework&lt;/a&gt;, DDD, and hexagonal/use-case architecture. It was the right call — the backend genuinely became enjoyable to work in, not something to fight against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening a legal entity while in immigration.&lt;/strong&gt; I'm not an EU citizen. I opened a UK LTD — something I'd never done in my life, in a country that isn't mine, in a second language. It's surprisingly straightforward once you start, but it took mental energy I hadn't budgeted for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing based on actual economics.&lt;/strong&gt; Not "what sounds reasonable" but walking through unit cost, comparable tools, where the free tier lines make sense, and why flat pricing over per-seat billing fits the developer audience I'm building for. That analysis took longer than I expected and I'm glad I didn't rush it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A full DevOps pipeline, built and maintained by me.&lt;/strong&gt; GitHub CI/CD, my own Kubernetes cluster, ArgoCD for GitOps deployments. I'm the infrastructure team. I'm also the backend. I'm also the frontend. I'm also the support desk.&lt;/p&gt;

&lt;p&gt;Nobody helped me build any of this. I'm not complaining — it's genuinely something I'm proud of, even though bragging isn't really my thing. I'm writing it down because I think solo builders often undersell how much ground they're covering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Registrations
&lt;/h2&gt;

&lt;p&gt;After the soft launch, a small group of closed beta testers — people I actually know and trust — tried the early version and gave honest feedback. They caught small things I'd stopped seeing — a missing translation here, a confusing label there. They pointed at specific moments where they got confused. They told me the onboarding wasn't landing.&lt;/p&gt;

&lt;p&gt;That feedback was more valuable than months of me staring at my own code. I'm genuinely grateful for it.&lt;/p&gt;

&lt;p&gt;And then real users — people I don't know — started showing up. Each one felt way more significant than it probably should have. After years as a hired engineer building things for other people's products, seeing a real stranger's account appear in &lt;em&gt;my&lt;/em&gt; system landed differently. It still does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blank Canvas Problem
&lt;/h2&gt;

&lt;p&gt;Here's what the beta feedback actually taught me.&lt;/p&gt;

&lt;p&gt;Omnismith's v1 onboarding worked like this: register → step through a tutorial → land in an empty project with nothing in it — no templates, no attributes, no data.&lt;/p&gt;

&lt;p&gt;I thought that was fine. The tutorial explained the concepts. The user had a clean slate to start.&lt;/p&gt;

&lt;p&gt;What actually happened: users finished the tutorial and then stared at an empty screen, having memorized a sequence of UI steps but with no immediate sense of &lt;em&gt;what&lt;/em&gt; to actually do with them. The mental model wasn't formed yet. And blank canvases are paralyzing — they're for people who already know what they want to paint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2 is different.&lt;/strong&gt; Now when you register, you don't get an empty project. You get a demo project pre-populated with a real dataset: a product catalog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzua973pg1r4zkom9e77.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzua973pg1r4zkom9e77.webp" alt="Omnismith demo project — product catalog with categories, brands, and products" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It has 3 templates — &lt;strong&gt;Category&lt;/strong&gt;, &lt;strong&gt;Brand&lt;/strong&gt;, and &lt;strong&gt;Product&lt;/strong&gt; — with 11 attributes across them. Some attributes are shared across templates (&lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Description&lt;/code&gt;). The Product template uses References to link each product to its Category and Brand, and even has a self-referencing "Related Product" link. There are 19 sample entities: 4 categories (Electronics, Furniture, Apparel, Books), 5 brands, and 10 products with real prices, stock status, release dates, and tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb9pnt1zfgzh1t6suxpk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb9pnt1zfgzh1t6suxpk.webp" alt="Omnismith v2 onboarding — interactive tour with contextual pointers" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's not a lot. But it's &lt;em&gt;enough&lt;/em&gt; to instantly understand the shape of the platform. There's something to click on. There's data to edit. The templates tell you "this is how structure works." The entities tell you "this is what your data looks like."&lt;/p&gt;

&lt;p&gt;On top of that, I added an interactive tour — something in the spirit of Intro.js — that walks you through the actual UI with contextual pointers. Not a wall of text in a help doc, but a live guide that highlights the element you're looking at right now. That combination — real data you can touch &lt;em&gt;plus&lt;/em&gt; a tour that shows you where to look — landed much better than v1's tutorial-then-empty-canvas approach.&lt;/p&gt;

&lt;p&gt;My beta testers stopped asking "what do I do now?" Which was the only metric that mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson That Should Have Been Obvious
&lt;/h2&gt;

&lt;p&gt;The blank canvas problem is not new. Every tool with a new user flow has solved or failed to solve it. But when it's &lt;em&gt;your&lt;/em&gt; tool, your mental model of it is so complete that you forget how much context you're carrying that your user doesn't have yet.&lt;/p&gt;

&lt;p&gt;You see a blank project and feel possibilities. Your user sees nothing.&lt;/p&gt;

&lt;p&gt;Show them a story they can edit. Don't make them write the opening chapter.&lt;/p&gt;

&lt;p&gt;That was probably the most valuable lesson of my early founder &lt;em&gt;days&lt;/em&gt;. Not a technical insight. Not an architecture decision. A UX realization delivered by real people being kind enough to tell me where they got stuck instead of just quietly leaving.&lt;/p&gt;

&lt;p&gt;If you're building a tool and your new user flow ends with "and now they have a blank canvas" — reconsider that.&lt;/p&gt;




&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;omnismith.io&lt;/a&gt;&lt;/strong&gt; — log in and see the demo project yourself&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://app.omnismith.io" rel="noopener noreferrer"&gt;app.omnismith.io&lt;/a&gt;&lt;/strong&gt; — free tier, no credit card&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://docs.omnismith.io" rel="noopener noreferrer"&gt;docs.omnismith.io&lt;/a&gt;&lt;/strong&gt; — now actually worth reading&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of the "I'm Building Omnismith in Public" series. &lt;a href="https://omnismith.io/blog/i-built-this-because-i-was-tired-of-building-admin-panels" rel="noopener noreferrer"&gt;Previous: I Built This Because I Was Tired of Building Admin Panels.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>programming</category>
      <category>ux</category>
      <category>startup</category>
    </item>
    <item>
      <title>I Built This Because I Was Tired of Building Admin Panels</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Mon, 16 Feb 2026 20:29:41 +0000</pubDate>
      <link>https://dev.to/homeless-coder/i-built-this-because-i-was-tired-of-building-admin-panels-4e97</link>
      <guid>https://dev.to/homeless-coder/i-built-this-because-i-was-tired-of-building-admin-panels-4e97</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the original founder-side version of the story. From post 3 onward, the series was rewritten in a calmer, more technical style.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's Feb 16, 2026, and I'm doing something that feels both exciting and terrifying: I'm opening &lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;Omnismith&lt;/a&gt; to the public. After several months of development, the landing page is live, payments are integrated, the app works. This is the moment.&lt;/p&gt;

&lt;p&gt;But let me back up and tell you why this exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem I Kept Running Into
&lt;/h2&gt;

&lt;p&gt;If you've worked as a developer-for-hire or in an agency, you know this pattern by heart:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client needs an admin panel&lt;/li&gt;
&lt;li&gt;You build it (again)&lt;/li&gt;
&lt;li&gt;"Can we add a field for X?"&lt;/li&gt;
&lt;li&gt;You write a migration, update the model, modify the form&lt;/li&gt;
&lt;li&gt;"Actually, can we track Y over time?"&lt;/li&gt;
&lt;li&gt;You add TimescaleDB, write aggregation queries, build charts&lt;/li&gt;
&lt;li&gt;"Who changed this value last week?"&lt;/li&gt;
&lt;li&gt;You implement audit logging&lt;/li&gt;
&lt;li&gt;Repeat for the next 15 clients&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every project had the same base requirements, just different &lt;em&gt;data structures&lt;/em&gt;. CRM for one client, inventory management for another, server monitoring for a third. Same features (CRUD, history, charts, access control), different schemas.&lt;/p&gt;

&lt;p&gt;At some point, I got tired of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The EAV Realization
&lt;/h2&gt;

&lt;p&gt;I started thinking: what if the &lt;em&gt;structure&lt;/em&gt; itself was data?&lt;/p&gt;

&lt;p&gt;This led me to Entity-Attribute-Value (EAV) architecture. Instead of hardcoding schemas in migrations, you define them in the UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attributes&lt;/strong&gt; are your building blocks (like table columns)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates&lt;/strong&gt; are your schemas (like table definitions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entities&lt;/strong&gt; are your actual data (like table rows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You want to track servers? Create attributes for &lt;code&gt;Hostname&lt;/code&gt;, &lt;code&gt;IP Address&lt;/code&gt;, &lt;code&gt;CPU Cores&lt;/code&gt;, &lt;code&gt;Memory GB&lt;/code&gt;. Combine them into a "Server" template. Done. No migration files. No deployment. No downtime.&lt;/p&gt;

&lt;p&gt;You want to add OS version? Create the attribute, add it to the template. It's available immediately.&lt;/p&gt;

&lt;p&gt;This isn't a new idea—Airtable does this beautifully, Notion databases do this, hell, even WordPress custom fields do a version of this. But I needed something that also handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metric ingestion&lt;/strong&gt; for time-series data (think Datadog, but for any entity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full audit history&lt;/strong&gt; (who changed what, when, and why)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper RBAC&lt;/strong&gt; (role-based + resource-level access control)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API-first architecture&lt;/strong&gt; (with OpenAPI spec, ready for integrations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built it. Using my own &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;modular PHP framework&lt;/a&gt; (yes, the one I wrote about before), PostgreSQL with TimescaleDB for time-series, and Angular 21 for the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes This Different From Airtable
&lt;/h2&gt;

&lt;p&gt;If you just need flexible schemas and basic CRUD, Airtable is fantastic. It's polished, has a great ecosystem, and works well for business teams. But here's what it &lt;em&gt;doesn't&lt;/em&gt; do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time-series metric ingestion.&lt;/strong&gt; Airtable stores snapshots—each record is a moment in time. Omnismith stores &lt;em&gt;timelines&lt;/em&gt;. You can push metrics via API every 30 seconds (CPU usage, temperature, API request counts, sensor readings) and query them like: &lt;em&gt;"Show me average CPU usage for the last 7 days."&lt;/em&gt; This isn't a workaround with linked records and formulas—it's TimescaleDB under the hood, built for this exact use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full audit history without manual versioning.&lt;/strong&gt; Every field change is logged automatically with who/what/when. No need to create separate "Version History" tables or complex automation rules to track changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI that executes, not suggests.&lt;/strong&gt; Airtable's AI generates formulas and field descriptions. Omnismith's AI &lt;em&gt;creates templates, adds attributes, inserts entities, and queries your data&lt;/em&gt;. It has direct function-calling access to your database through the OpenAPI spec. It's not a documentation helper—it's a schema builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flat pricing for developers.&lt;/strong&gt; $99/month gets you 50,000 entities with all features unlocked. Airtable Team costs $20/seat, so 5 users = $100/month for 50K records—but you're locked into per-seat billing even if you're just using the API. Need RBAC? That's Business tier at $45/seat. Need audit logs? Same. Omnismith: everything's included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API-first design.&lt;/strong&gt; Full OpenAPI spec generated from code. Every UI action has an API equivalent. Built for developers who need programmatic control over their data platform, not just business users clicking through forms.&lt;/p&gt;

&lt;p&gt;If Airtable is for marketing teams managing campaigns, Omnismith is for developers building IoT platforms, SaaS backends, or internal tools that need both flexible schemas &lt;em&gt;and&lt;/em&gt; time-series analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Assistant That Actually Works
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. I added an AI Assistant—but not the kind that gives you generic advice or writes code snippets. This one &lt;em&gt;executes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It's powered by Google Gemini 2.5 and has direct access to your Omnismith data through function calling. Watch what happens when you talk to it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Create a Server template with hostname, IP, OS, and CPU cores"&lt;/p&gt;

&lt;p&gt;The assistant doesn't just &lt;em&gt;tell&lt;/em&gt; you how to do it. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a StringAttribute for "Hostname"&lt;/li&gt;
&lt;li&gt;Creates a StringAttribute for "IP Address"
&lt;/li&gt;
&lt;li&gt;Creates a ListAttribute for "OS" with options like "Ubuntu", "CentOS", "Windows"&lt;/li&gt;
&lt;li&gt;Creates a NumberAttribute for "CPU Cores"&lt;/li&gt;
&lt;li&gt;Creates a Template called "Server"&lt;/li&gt;
&lt;li&gt;Associates all 4 attributes with the template&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You see all of this happen in real-time—streaming responses with visible tool execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Add a new server called web-prod-01 with IP 10.0.1.50, Ubuntu, 8 cores"&lt;/p&gt;

&lt;p&gt;It creates the entity, fills in all the attributes. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Show me all servers with more than 4 CPU cores"&lt;/p&gt;

&lt;p&gt;It queries your data and returns the results in a readable format.&lt;/p&gt;

&lt;p&gt;This isn't a chatbot that &lt;em&gt;suggests&lt;/em&gt; changes. It's an agent that &lt;em&gt;makes&lt;/em&gt; them. The LLM proxy layer (a custom Fastify server I built) handles streaming, tool orchestration, and authentication. The tools themselves are auto-generated from the OpenAPI spec. Everything is schema-aware.&lt;/p&gt;

&lt;p&gt;For developers building systems for non-technical clients, this is huge. Your client can literally describe what they need in plain language, and the assistant scaffolds it. No training documentation, no "click here, then here, then submit" tutorials. Just conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Actually Do With It
&lt;/h2&gt;

&lt;p&gt;Right now, Omnismith is being used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal AI assistants&lt;/strong&gt; (data from Telegram bots, reminder systems)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoT device monitoring&lt;/strong&gt; (temperature, humidity, motion sensors pushing metrics via API)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User consent tracking&lt;/strong&gt; (GDPR compliance, versioned agreements)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server fleet management&lt;/strong&gt; (infrastructure inventory + metric dashboards)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product catalogs&lt;/strong&gt; (headless backend for custom storefronts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The free tier gives you 150 entities and 7 days of history retention—enough to validate an idea or run a small personal project. The Scale tier ($99/month) gives you 50,000 entities, 180 days of retention, and all features unlocked: AI Assistant, full RBAC, audit logging, dashboards, automations—no per-seat charges, no premium tiers. (Compare: Airtable Team is $20/seat for similar record limits, but RBAC and audit logs require Business tier at $45/seat.)&lt;/p&gt;

&lt;p&gt;Full OpenAPI spec, so you can integrate it with anything. I use it via API tokens from custom scripts. The UI is in 8 languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack (For the Curious)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.4 (strict types, readonly properties, enums, attributes)&lt;/li&gt;
&lt;li&gt;Custom modular framework (explicit DI, hexagonal architecture, DDD)&lt;/li&gt;
&lt;li&gt;PostgreSQL + TimescaleDB&lt;/li&gt;
&lt;li&gt;Transactional outbox pattern for domain events&lt;/li&gt;
&lt;li&gt;OpenAPI spec auto-generated from PHP attributes&lt;/li&gt;
&lt;li&gt;PHPUnit: Tests: 996, Assertions: 5775&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular 21 (zoneless, standalone components)&lt;/li&gt;
&lt;li&gt;Angular Signals for state management&lt;/li&gt;
&lt;li&gt;Angular Material + TailwindCSS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rxResource&lt;/code&gt; for data fetching&lt;/li&gt;
&lt;li&gt;AG Charts for visualizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom LLM proxy (Fastify + TypeScript)&lt;/li&gt;
&lt;li&gt;Google Gemini 2.5&lt;/li&gt;
&lt;li&gt;Function calling with OpenAPI-derived tools&lt;/li&gt;
&lt;li&gt;SSE streaming with visible tool execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've written about the framework before on dev.to (see my &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;modular architecture series&lt;/a&gt;), and yes, Omnismith is built entirely on it. That's validation #1 that the architecture actually works at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Focus &amp;amp; Roadmap
&lt;/h2&gt;

&lt;p&gt;I'm building this in public and prioritizing core stability over "feature bloat". While it is in beta, I'm focused on perfecting the desktop experience and core data visualization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current Visuals: High-performance line charts, stats, gauges, and lists (Advanced chart types in development).&lt;/li&gt;
&lt;li&gt;Workflow: Currently focused on core data-handling; a visual automation builder (aka "Business Processes") to trigger automated data updates on specific events is the next major milestone.&lt;/li&gt;
&lt;li&gt;Mobile: Optimized for desktop power-users; responsive mobile tweaks are rolling out weekly, but it's already usable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a "replace everything" tool yet - it's a lean, functional engine for users who need simplicity and effectiveness without the enterprise clutter. It's functional, tested, and solving real problems for the handful of users I've soft-launched with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm Telling You This
&lt;/h2&gt;

&lt;p&gt;I'm a developer. I know how to write code, architect systems, and ship products. What I don't know how to do is &lt;em&gt;market&lt;/em&gt; things with loud, empty promises.&lt;/p&gt;

&lt;p&gt;I'm not going to tell you Omnismith is "revolutionary" or "unprecedented" or will "transform your workflow." It's a tool. It solves a specific set of problems (flexible schemas + time-series + audit history) better than cobbling together Airtable + Datadog + custom code.&lt;/p&gt;

&lt;p&gt;If you've ever built an admin panel and thought "there has to be a better way," this might be that way. If you need to track &lt;em&gt;things&lt;/em&gt; (servers, products, sensors, contracts, whatever) and see how they change over time, this is built for that.&lt;/p&gt;

&lt;p&gt;Try it. Break it. Tell me what's wrong. I'm listening.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;omnismith.io&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://app.omnismith.io" rel="noopener noreferrer"&gt;app.omnismith.io&lt;/a&gt;&lt;/strong&gt; (150 entities free, no credit card)&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://swagger.omnismith.io" rel="noopener noreferrer"&gt;swagger.omnismith.io&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; The AI Assistant demo is real. Log in, hit the AI Assistant menu, and try: &lt;em&gt;"Create a Product template with name, price, and SKU"&lt;/em&gt;. Watch it work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S.&lt;/strong&gt; If you're curious about the modular framework powering all of this, I've written a series about it: &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;Solving PHP's Module Coupling Problem&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>ai</category>
      <category>startup</category>
    </item>
    <item>
      <title>Auto-Discovering Console Commands in Power Modules</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Thu, 16 Oct 2025 00:05:33 +0000</pubDate>
      <link>https://dev.to/homeless-coder/auto-discovering-console-commands-in-power-modules-4j8a</link>
      <guid>https://dev.to/homeless-coder/auto-discovering-console-commands-in-power-modules-4j8a</guid>
      <description>&lt;p&gt;A couple of days ago, I was working on a PHP project that uses the Power Modules framework and Symfony Console, and I realized I was repeating the same command registration pattern across multiple projects. Time to build something better!&lt;/p&gt;

&lt;p&gt;For those new to the series: Power Modules is a modular PHP framework where each module encapsulates its own logic and dependencies, communicating through well-defined interfaces while maintaining strict boundaries.&lt;/p&gt;

&lt;p&gt;I already had a working setup/boilerplate for registering console commands exported from a Power Module (using the &lt;code&gt;ExportsComponents&lt;/code&gt; interface) that I copy-pasted across projects. It follows the Power Modules framework principles: keep modules encapsulated and their dependencies private, and delegate the instantiation and dependency resolution to their DI container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrdersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UsersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other modules&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/../'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'My Console Application'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0.1.0'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$modules&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$module&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// $exportedComponent is just a class-string that references to a power module DI container&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;This works, but it felt clunky repeating this boilerplate across projects. I knew that Symfony DI can manage console commands, and I wanted to see if I could build something that automatically discovers and registers console commands from different modules. So I thought: Why not apply the &lt;code&gt;PowerModuleSetup&lt;/code&gt; concept for this? (you can read more about it here: &lt;a href="https://dev.to/homeless-coder/the-night-i-discovered-id-built-something-revolutionary-and-didnt-know-it-2oik"&gt;The Night I Discovered I'd Built Something Revolutionary (And Didn't Know It)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Solution
&lt;/h2&gt;

&lt;p&gt;The implementation was quite straightforward - create a &lt;code&gt;PowerModuleSetup&lt;/code&gt; that does the same thing as the above code, but in a more modular and reusable way.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Iteration: Direct Registration
&lt;/h3&gt;

&lt;p&gt;This &lt;code&gt;PowerModuleSetup&lt;/code&gt; bridges Symfony Console with the Power Modules framework's modular architecture. Modules export console commands while maintaining encapsulation principles, and commands are auto-discovered and registered into a central &lt;code&gt;Console\Application&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsoleCommandsSetup&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModuleSetup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt; &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PowerModuleSetupDto&lt;/span&gt; &lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setupPhase&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nc"&gt;SetupPhase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Lazy-Loading with ContainerCommandLoader
&lt;/h3&gt;

&lt;p&gt;I wanted to take it further with deferred command instantiation. Symfony's &lt;code&gt;ContainerCommandLoader&lt;/code&gt; loads commands from a DI container on-demand. Since &lt;code&gt;ExportsComponentsSetup&lt;/code&gt; already registers exported components in the root container, I could leverage that. This meant updating the setup to use a two-phase approach: collect commands in the Pre phase and register them in the Post phase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsoleCommandsSetup&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModuleSetup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Application&lt;/span&gt; &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?CommandLoaderInterface&lt;/span&gt; &lt;span class="nv"&gt;$commandLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @var array&amp;lt;string,class-string&amp;lt;Command&amp;gt;&amp;gt; $commandMap
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$commandMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PowerModuleSetupDto&lt;/span&gt; &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setupPhase&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;SetupPhase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Pre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// PRE phase: collect all commands to be registered later&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_subclass_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReflectionClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AsCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$attribute&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;newInstance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandLoader&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContainerCommandLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setCommandLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandLoader&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Now I have a reusable &lt;code&gt;PowerModuleSetup&lt;/code&gt; that can be added to any Power Modules application to automatically discover and register console commands from modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/../'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrdersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UsersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsoleCommandsSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Console application is now available with all module commands registered&lt;/span&gt;
&lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The complete implementation is available in the repository: &lt;a href="https://github.com/power-modules/console" rel="noopener noreferrer"&gt;power-modules/console&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install via Composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require power-modules/console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern eliminated the boilerplate I was copy-pasting and made command registration automatic. If you're building modular applications with Power Modules, this setup might save you some time too.&lt;/p&gt;

</description>
      <category>php</category>
      <category>software</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From Vision to Visualization: Building Pluggable Architecture for PHP</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Tue, 30 Sep 2025 18:24:49 +0000</pubDate>
      <link>https://dev.to/homeless-coder/from-vision-to-visualization-building-pluggable-architecture-for-php-1olb</link>
      <guid>https://dev.to/homeless-coder/from-vision-to-visualization-building-pluggable-architecture-for-php-1olb</guid>
      <description>&lt;h2&gt;
  
  
  The spark: from architecture to ecosystems 🔌
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;first post&lt;/a&gt;, I shared how explicit module boundaries (imports/exports) make architecture visible. In my &lt;a href="https://dev.to/homeless-coder/the-night-i-discovered-id-built-something-revolutionary-and-didnt-know-it-2oik"&gt;second post&lt;/a&gt;, I realized that same foundation quietly unlocked an ecosystem pattern that goes beyond web apps.&lt;/p&gt;

&lt;p&gt;This post shows the first concrete piece of that ecosystem story: a dependency‑injection‑native plugin system with static analysis support (Layer 1), a real extension point in the dependency graph module (Layer 2), and a third‑party implementation you can use today—the Mermaid renderers (Layer 3).&lt;/p&gt;

&lt;p&gt;You'll see how plugins are discovered automatically, resolved with DI, and used to visualize your architecture with one command. This foundation enables everything from content filter pipelines to payment gateway adapters—patterns we'll explore in Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem layers, now practical
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Layer 1: Foundation for plugins (generic, domain‑agnostic)&lt;/li&gt;
&lt;li&gt;Layer 2: Domain‑specific cores exposing plugin points (e.g., dependency‑graph → Renderer)&lt;/li&gt;
&lt;li&gt;Layer 3: Third‑party plugins (e.g., Mermaid renderers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll walk through all three, end‑to‑end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1 — The plugin foundation (generic but powerful) 🧩
&lt;/h2&gt;

&lt;p&gt;The goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugins are first‑class citizens with metadata&lt;/li&gt;
&lt;li&gt;Registries provide IDE/static analysis support (via PHPDoc generics) and lazy loading (instantiate on demand)&lt;/li&gt;
&lt;li&gt;Discovery is automatic at app boot, integrated with module isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key pieces (&lt;a href="https://packagist.org/packages/power-modules/plugin" rel="noopener noreferrer"&gt;&lt;code&gt;power-modules/plugin&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Plugin&lt;/code&gt;: every plugin returns &lt;code&gt;PluginMetadata&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PluginRegistry&amp;lt;TPlugin&amp;gt;&lt;/code&gt;: register, list, and instantiate plugins (generic annotation for IDE support)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GenericPluginRegistry&lt;/code&gt;: default, DI‑native implementation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProvidesPlugins&amp;lt;TPlugin&amp;gt;&lt;/code&gt;: a module declares its plugins for one or more registries&lt;/li&gt;
&lt;li&gt;Setups:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GenericPluginRegistrySetup&lt;/code&gt;: provides a default registry in root&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PluginRegistryModuleConvenienceSetup&lt;/code&gt;: makes the registry injectable in modules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PluginRegistrySetup&lt;/code&gt;: discovers modules that implement &lt;code&gt;ProvidesPlugins&lt;/code&gt; and registers their plugins&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;A tiny example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\App\ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\PowerModule\Contract\PowerModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\Container\ConfigurableContainerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\Contract\Plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\PluginMetadata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\Contract\PluginRegistry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\Contract\ProvidesPlugins&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\PowerModule\Setup\PluginRegistrySetup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TextProcessor&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;TextProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPluginMetadata&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;PluginMetadata&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PluginMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Uppercase'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1.0.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Converts text to UPPERCASE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;strtoupper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextPluginModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProvidesPlugins&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPlugins&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt; &lt;span class="c1"&gt;// declare plugins&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ConfigurableContainerInterface&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// DI binding&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistrySetup&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withDefaults&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextPluginModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cd"&gt;/** @var PluginRegistry&amp;lt;TextProcessor&amp;gt; $registry */&lt;/span&gt;
&lt;span class="nv"&gt;$registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$plugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$registry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makePlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$plugin&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// HELLO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At boot, &lt;code&gt;PluginRegistrySetup&lt;/code&gt; scans modules that implement &lt;code&gt;ProvidesPlugins&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For each plugin class, it registers a (class → owning container) mapping in the root registry&lt;/li&gt;
&lt;li&gt;At runtime, &lt;code&gt;makePlugin()&lt;/code&gt; resolves the instance from that module's container (full DI)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Layer 2 — The dependency graph exposes a plugin point 🧭
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://packagist.org/packages/power-modules/dependency-graph" rel="noopener noreferrer"&gt;&lt;code&gt;power-modules/dependency-graph&lt;/code&gt;&lt;/a&gt; module collects your modules and their relationships during app bootstrap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DependencyGraphSetup&lt;/code&gt; builds a &lt;code&gt;DependencyGraph&lt;/code&gt; from &lt;code&gt;ModuleNode&lt;/code&gt;s and their imports&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Renderer&lt;/code&gt; is a plugin contract for turning that graph into outputs (text, JSON, Mermaid, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RendererPluginRegistry&lt;/code&gt; (extends &lt;code&gt;GenericPluginRegistry&amp;lt;Renderer&amp;gt;&lt;/code&gt;) is exported by &lt;code&gt;RendererModule&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Renderer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DependencyGraph&lt;/span&gt; &lt;span class="nv"&gt;$graph&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getFileExtension&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g. mmd, json&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getMimeType&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getDescription&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&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 us a clean "slot" where anyone can add a new renderer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3 — Third‑party Mermaid renderers 🧪
&lt;/h2&gt;

&lt;p&gt;Now here's where it gets interesting. This repository (&lt;a href="https://packagist.org/packages/power-modules/dependency-graph-mermaid" rel="noopener noreferrer"&gt;&lt;code&gt;power-modules/dependency-graph-mermaid&lt;/code&gt;&lt;/a&gt;) is a third‑party plugin package that extends the dependency-graph module with visual renderers.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MermaidRendererModule&lt;/code&gt; registers three plugins implementing &lt;code&gt;Renderer&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flowchart&lt;/strong&gt;: &lt;code&gt;MermaidGraph&lt;/code&gt; (LR direction, shows imports as labeled arrows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class diagram&lt;/strong&gt;: &lt;code&gt;MermaidClassDiagram&lt;/code&gt; (TB direction + YAML frontmatter, exports as class members)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline&lt;/strong&gt;: &lt;code&gt;MermaidTimeline&lt;/code&gt; (dependency levels as phases, Infrastructure/Domain sections)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're regular plugins declared via &lt;code&gt;ProvidesPlugins&amp;lt;Renderer&amp;gt;&lt;/code&gt; and discovered automatically—no special wiring needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Visual tour: your architecture, rendered
&lt;/h2&gt;

&lt;p&gt;The best part? You get immediate visual feedback. Below are small, self‑contained Mermaid snippets inspired by the &lt;a href="https://github.com/power-modules/dependency-graph-mermaid/tree/main/examples/ecommerce/mermaid" rel="noopener noreferrer"&gt;ecommerce example&lt;/a&gt;. You can paste them into &lt;a href="https://mermaid.live/" rel="noopener noreferrer"&gt;Mermaid Live&lt;/a&gt; or VS Code's Mermaid preview to see them rendered.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Flowchart (modules and imports)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faljmlyqpnw23hl8ofqm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faljmlyqpnw23hl8ofqm7.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Class diagram (exports as members, imports as dashed deps)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqabv1ypfklqwqi82jiea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqabv1ypfklqwqi82jiea.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Timeline (boot phases by dependency levels)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9rnbye2s6o1lns1kjfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9rnbye2s6o1lns1kjfs.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tip: in code, &lt;code&gt;MermaidTimeline&lt;/code&gt; computes phases Kahn‑style and separates Infrastructure/Domain with a simple classifier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete setup pattern 🚀
&lt;/h2&gt;

&lt;p&gt;Here's the complete setup pattern—replace the example modules with your own:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the necessary setups and modules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DependencyGraphSetup()&lt;/code&gt; to collect the graph during app bootstrap&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;...PluginRegistrySetup::withDefaults()&lt;/code&gt; to enable plugin discovery&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RendererModule&lt;/code&gt; to expose the &lt;code&gt;RendererPluginRegistry&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MermaidRendererModule&lt;/code&gt; to register the three Mermaid plugins
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\App\ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\PowerModule\Setup\DependencyGraphSetup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Renderer\RendererModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Renderer\Mermaid\MermaidRendererModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Graph\DependencyGraph&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Renderer\RendererPluginRegistry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\PowerModule\Setup\PluginRegistrySetup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DependencyGraphSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistrySetup&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withDefaults&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;// your app modules&lt;/span&gt;
        &lt;span class="nc"&gt;App\User\UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Product\ProductModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Order\OrderModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Payment\PaymentModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Notification\NotificationModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Database\DatabaseModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// plugin point + plugins&lt;/span&gt;
        &lt;span class="nc"&gt;RendererModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;MermaidRendererModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DependencyGraph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$renderers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RendererPluginRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$renderers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRegisteredPlugins&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$rendererClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$renderers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makePlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rendererClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$renderer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$graph&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/mermaid/out.'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$renderer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFileExtension&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$code&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;Want ready‑made scripts? Check out &lt;a href="https://github.com/power-modules/dependency-graph-mermaid/tree/main/examples" rel="noopener noreferrer"&gt;&lt;code&gt;examples&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters (and how teams use it) 📌
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For refactoring:&lt;/strong&gt; Spot central "god" modules that import from everyone, or unused modules that export but no one imports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For onboarding:&lt;/strong&gt; Give new teammates a real map—not just "here's the codebase," but "here's how modules connect and what they export."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For architecture reviews:&lt;/strong&gt; Validate intended boundaries before they calcify. Catch cycles early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For documentation:&lt;/strong&gt; Check in generated &lt;code&gt;.mmd&lt;/code&gt; files alongside code. PRs that change module relationships show visual diffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For evolution planning:&lt;/strong&gt; Identify natural seams for service extraction—modules with low in‑degree and clear export contracts are good candidates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For CI guardrails:&lt;/strong&gt; Add analyzers to fail builds on cycles or unexpected coupling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Foundation: &lt;code&gt;composer require power-modules/plugin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Graph + plugin point: &lt;code&gt;composer require power-modules/dependency-graph&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Mermaid renderers: &lt;code&gt;composer require power-modules/dependency-graph-mermaid&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then add the setups and modules as shown above, render, and paste the &lt;code&gt;.mmd&lt;/code&gt; into Mermaid Live.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next 🔮
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 2: Building your own plugin ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom registries with domain-specific validation (payment gateways, content filters)&lt;/li&gt;
&lt;li&gt;Config-driven plugin pipelines (tenant-specific processing steps)&lt;/li&gt;
&lt;li&gt;Metadata-driven admin UIs (plugin catalogs without instantiation)&lt;/li&gt;
&lt;li&gt;Real patterns: CMS filters, discount strategies, ETL steps, gateway adapters&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/framework" rel="noopener noreferrer"&gt;power-modules/framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin system&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/plugin" rel="noopener noreferrer"&gt;power-modules/plugin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency graph&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/dependency-graph" rel="noopener noreferrer"&gt;power-modules/dependency-graph&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mermaid renderers&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/dependency-graph-mermaid" rel="noopener noreferrer"&gt;power-modules/dependency-graph-mermaid&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try the examples&lt;/strong&gt;: Clone the repository and run &lt;code&gt;php examples/ecommerce/generate.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm building this ecosystem in the open. If you try it, build a renderer, or have ideas for Part 2—drop a comment or connect with me!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What architectural challenges keep you up at night? Have you tried similar visualization approaches?&lt;/strong&gt; 💬&lt;/p&gt;

</description>
      <category>php</category>
      <category>architecture</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
