<?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>Forem: Dmitry Amelchenko</title>
    <description>The latest articles on Forem by Dmitry Amelchenko (@dmitryame).</description>
    <link>https://forem.com/dmitryame</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F138260%2Feadc6287-2299-4820-b1a5-0fb62e829db0.png</url>
      <title>Forem: Dmitry Amelchenko</title>
      <link>https://forem.com/dmitryame</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dmitryame"/>
    <language>en</language>
    <item>
      <title>From Vibe Coding to SDD: Why the Future of Engineering is Architecture</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Mon, 30 Mar 2026 23:02:14 +0000</pubDate>
      <link>https://forem.com/dmitryame/from-vibe-coding-to-sdd-why-the-future-of-engineering-is-architecture-2ae5</link>
      <guid>https://forem.com/dmitryame/from-vibe-coding-to-sdd-why-the-future-of-engineering-is-architecture-2ae5</guid>
      <description>&lt;h1&gt;
  
  
  The Architectural Shift
&lt;/h1&gt;

&lt;p&gt;The era of writing code by hand is becoming a relic of the past. As we move deeper into the age of Generative AI, the role of the software engineer is undergoing a fundamental phase shift: from the &lt;strong&gt;depth&lt;/strong&gt; of manual syntax to the &lt;strong&gt;breadth&lt;/strong&gt; of architectural orchestration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Entropy of "Vibe Coding"
&lt;/h2&gt;

&lt;p&gt;"Vibe coding"—relying on AI to generate code based on loose prompts and "vibes"—is an excellent entry point, but it carries a hidden tax: &lt;strong&gt;architectural entropy&lt;/strong&gt;. Without a rigorous framework, rapid AI generation leads to inconsistencies, technical drift, and a fragmented codebase that no human brain can fully map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Spec-Driven Development (SDD)
&lt;/h2&gt;

&lt;p&gt;Spec-Driven Development is the "all-in" solution to AI-driven engineering. It shifts the focus from the &lt;em&gt;output&lt;/em&gt; (the code) to the &lt;em&gt;intent&lt;/em&gt; (the specification). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Engineer as Architect:&lt;/strong&gt; In SDD, writing code is the simplest and fastest part of the cycle. The real work happens in refining the "spec"—ensuring requirements are precise, edge cases are covered, and external dependencies are identified upfront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Superiority:&lt;/strong&gt; While a human engineer will inevitably miss details in a massive project, GenAI can hold the entire context in memory. By using SDD, the engineer acts as a high-level validator, challenging assumptions and directing the AI to maintain a consistent vision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The End of the Jira Bug Cycle:&lt;/strong&gt; In an SDD workflow, the feedback loop is nearly instantaneous. Instead of an architect writing a spec and waiting weeks for a developer to validate it, the "newborn architect" (formerly the developer) can validate assumptions on the spot.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Economics of Hyper-Productivity
&lt;/h2&gt;

&lt;p&gt;The transition to SDD isn't just a qualitative win; it’s a massive economic lever. In a recent month-long experiment with Spec-driven development, I burned through over 1,000% of my monthly token allowance. The cost? &lt;strong&gt;$135&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;To put that in perspective:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$135 (Roughly one hour of a mid-level developer's time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output Volume&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Equivalent to &lt;strong&gt;3 months&lt;/strong&gt; of full-time manual engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quality Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Precision bug identification exceeding manual capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Path Forward: Breadth Over Depth
&lt;/h2&gt;

&lt;p&gt;There will be resistance. Engineers who have spent years perfecting their manual craft may feel marginalized. However, the argument is not that these skills are obsolete, but that they must be elevated. &lt;/p&gt;

&lt;p&gt;We are moving toward a world where &lt;strong&gt;every pull request is a spec&lt;/strong&gt;. The goal is to turn every developer into an architect—someone who understands the entire picture, manages dependencies, and leverages AI to execute with a speed and accuracy that was previously impossible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The conclusion is clear:&lt;/strong&gt; Writing code by hand is becoming "lame." The future belongs to those who positions themselves can master the specification.&lt;/p&gt;




&lt;h2&gt;
  
  
  Entry-Level Jobs Shift to Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Core Thesis&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The entry-level software engineer role is structurally obsolete. The new floor for any developer entering the workforce is architectural thinking, not code execution. GenAI compresses the time required to acquire that architectural fluency, but the expectation shift is immediate and non-negotiable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supporting Arguments&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Everyone becomes an architect" is not aspirational framing. It is a baseline requirement. The question of what happens to junior dev jobs is already answered: those jobs, as traditionally defined, do not survive the transition.&lt;/li&gt;
&lt;li&gt;GenAI accelerates skill acquisition toward architectural competency, but it does not lower the bar. It raises the starting line.&lt;/li&gt;
&lt;li&gt;This aligns directly with SDD mandate: the "developer as architect/orchestrator" identity is not a senior-level privilege. It is the entry condition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Open Questions / Blind Spots&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hiring pipeline implications.&lt;/strong&gt; If architectural thinking is the new floor, how do companies current hiring rubric, leveling framework, and onboarding process need to change? The thesis demands a corresponding talent strategy, not just a cultural declaration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The acceleration claim needs stress-testing.&lt;/strong&gt; GenAI helps acquire skills "in no time" is an assertion, not a proof. The gap between GenAI-assisted architectural intuition and genuine systems judgment (failure modes, tradeoffs, organizational context) may be wider than the framing suggests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resistance vector.&lt;/strong&gt; This argument is most threatening to mid-level engineers who built identity around depth. The framing addresses new entrants but sidesteps the harder conversation: what does this mean for the engineer who has been writing code for five years and is not yet an architect?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>There is C, there is C#, and there is Java—which is effectively C♭.</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Thu, 12 Mar 2026 16:12:09 +0000</pubDate>
      <link>https://forem.com/dmitryame/there-is-c-there-is-c-and-there-is-java-which-is-effectively-c-4gc8</link>
      <guid>https://forem.com/dmitryame/there-is-c-there-is-c-and-there-is-java-which-is-effectively-c-4gc8</guid>
      <description></description>
    </item>
    <item>
      <title>The End of Vibe Coding</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Sun, 08 Mar 2026 03:49:49 +0000</pubDate>
      <link>https://forem.com/dmitryame/the-end-of-vibe-coding-2e78</link>
      <guid>https://forem.com/dmitryame/the-end-of-vibe-coding-2e78</guid>
      <description>&lt;p&gt;The "Move Fast and Break Things" era has met its match in Generative AI. We are currently witnessing a split in the engineering world. On one side, there is &lt;strong&gt;Vibe Coding&lt;/strong&gt;: a frantic, prompt-heavy workflow where developers describe a goal, "re-roll" until the code looks right, and pray the edge cases don't implode in production. On the other, there is &lt;strong&gt;Spec-Driven Development (SDD)&lt;/strong&gt;: a disciplined, architectural approach that treats AI not as a magic wand, but as a high-velocity compiler for human intent.&lt;/p&gt;

&lt;p&gt;If you are building mission-critical systems, the "vibe" is no longer enough. Here is why the industry is shifting toward the Power Inversion—and how you can lead the transition.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Lethal Trifecta of Vibe Coding
&lt;/h3&gt;

&lt;p&gt;Vibe coding feels like a superpower during a weekend hackathon. You aren't bogged down by syntax; you are describing a vision. However, when that prototype needs to become a foundation, three structural limits emerge:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Assumption Problem:&lt;/strong&gt; AI hates ambiguity. If your prompt is vague, the model will aggressively fill in the blanks with its own assumptions. This leads to code that looks perfect but is quietly, dangerously wrong.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Context Decay:&lt;/strong&gt; AI models have the "memory of a goldfish." As a project grows, the model forgets the architectural decisions you made last week, leading to a messy patchwork of local fixes.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Accountability Gap:&lt;/strong&gt; In vibe coding, the code is the only source of truth. You lose the audit trail of &lt;em&gt;why&lt;/em&gt; a decision was made, making professional governance impossible.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution: The Power Inversion
&lt;/h3&gt;

&lt;p&gt;To harness AI without the chaos, we must embrace the &lt;strong&gt;Power Inversion&lt;/strong&gt;. In the old world, "Code was King"—if the docs and the code disagreed, the docs were wrong. In the SDD world, the &lt;strong&gt;Specification is the Truth&lt;/strong&gt;. If the code deviates from the spec, the code is broken and must be fixed.&lt;/p&gt;

&lt;p&gt;This shift transforms the developer's role from a "prompter" to an &lt;strong&gt;Architect of Intent&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  The SDD Framework: Five Non-Negotiables
&lt;/h3&gt;

&lt;p&gt;To move from "unsupervised intern" AI to a "high-leverage collaborator," your workflow must integrate these five pillars:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Executable Intent
&lt;/h4&gt;

&lt;p&gt;Your specifications cannot be dusty PDFs. They must be living, machine-readable documents (Markdown) that live in your Git repo. A spec is "executable" when it is so unambiguous that an AI can derive a technical plan and test suite from it without guessing.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. The Clarification Gate
&lt;/h4&gt;

&lt;p&gt;AI agents are eager to please, which makes them prone to hallucinating business logic. Implement a &lt;strong&gt;Clarification Gate&lt;/strong&gt;: the agent is forbidden from coding until it has surfaced all ambiguities. If a requirement touches "Money," "Security," or "Data Deletion," the threshold for ambiguity is zero.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. The Project Constitution
&lt;/h4&gt;

&lt;p&gt;Every project needs a &lt;code&gt;CONSTITUTION.md&lt;/code&gt; (different SDD frameworks may name it differently, but the concept remains the same). This file encodes your "Architectural DNA"—naming conventions, forbidden libraries, and error-handling patterns. It ensures that code written a year from now feels like it was written by the same senior architect who started the project.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Atomic Decomposition (Micro-PRs)
&lt;/h4&gt;

&lt;p&gt;Speed is a liability if the output is too large to review. Break features into tiny, verifiable tasks. If a human cannot verify a code change in two minutes, the task is too big. This "Small Steps Philosophy" prevents &lt;strong&gt;Agent Drift&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Verification-First Governance
&lt;/h4&gt;

&lt;p&gt;Done doesn't mean "the demo works." Done means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The intent is approved.&lt;/li&gt;
&lt;li&gt;The tests enforcing that intent are passing.&lt;/li&gt;
&lt;li&gt;The code is a derived artifact of the spec.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The ROI of Rigor
&lt;/h3&gt;

&lt;p&gt;Critics argue that SDD slows you down. The data suggests otherwise. By shifting effort &lt;strong&gt;upstream&lt;/strong&gt;—spending more time on the "What" and the "Why"—you virtually eliminate the downstream costs of debugging, rework, and technical debt. &lt;/p&gt;

&lt;p&gt;Companies like &lt;strong&gt;Airbnb&lt;/strong&gt; and &lt;strong&gt;Google&lt;/strong&gt; have proven this at scale, using structured AI workflows to complete year-long migrations in a matter of weeks. They didn't just get faster; they got more deterministic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion: Your New Superpower
&lt;/h3&gt;

&lt;p&gt;In an era where AI can write code nearly for free, the bottleneck is no longer typing speed or framework knowledge. The new bottleneck is &lt;strong&gt;clarity of thought&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Your value as an engineer is now measured by your ability to define intent with absolute precision. Stop serving the code. Start making the code serve your intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is your spec a North Star, or just a vague suggestion? The choice defines the quality of everything you build.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>engineering</category>
      <category>testing</category>
      <category>workflow</category>
    </item>
    <item>
      <title>From 18 to 3 Hours: When Gen AI Overdelivers (or Did It?)</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Thu, 14 Aug 2025 02:06:39 +0000</pubDate>
      <link>https://forem.com/dmitryame/how-good-is-gen-ai-at-providing-estimates-and-then-holding-itself-accountable-for-meeting-them-3pee</link>
      <guid>https://forem.com/dmitryame/how-good-is-gen-ai-at-providing-estimates-and-then-holding-itself-accountable-for-meeting-them-3pee</guid>
      <description>&lt;p&gt;I wanted to implement the dark mode on my &lt;a href="https://github.com/echowaves/WiSaw" rel="noopener noreferrer"&gt;pet project mobile app&lt;/a&gt; for a while -- could never find enough time and motivation to do it until now. Last night I finally started chatting to Github Copilot (Claude 4 sonnet model):&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%2Ftx8kqkct2lolikqh11to.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%2Ftx8kqkct2lolikqh11to.png" alt=" " width="800" height="577"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fzu942nkdya4wklwwf44t.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%2Fzu942nkdya4wklwwf44t.png" alt=" " width="800" height="577"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fopbonw2kyav0xwsmfywb.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%2Fopbonw2kyav0xwsmfywb.png" alt=" " width="800" height="544"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fyjsyo60hj6v8o6as7a2h.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%2Fyjsyo60hj6v8o6as7a2h.png" alt=" " width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;18 hours? No way. It will take me at least 4 weeks working on this little project solo for 2-3 hours a day after work. &lt;/p&gt;

&lt;p&gt;Let's see what we (Copilot and I) were able to accomplish:&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%2Fo0pggxkassril2yotgq9.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%2Fo0pggxkassril2yotgq9.png" alt=" " width="800" height="862"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fkriihk5shl4vxwo6lseu.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%2Fkriihk5shl4vxwo6lseu.png" alt=" " width="800" height="862"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2F9n88f7wh7ve4a96nr4bv.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%2F9n88f7wh7ve4a96nr4bv.png" alt=" " width="800" height="862"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are lot more screens, I'm showing only the most significant ones here.&lt;/p&gt;

&lt;p&gt;It took about 3 hours total, talking to Copilot, asking questions, watching youtube videos while waiting for Copilot to complete the response to a particular prompt, then iterate again, and again, and again, until it was ... done!&lt;br&gt;
It touched pretty much every single file in the project. &lt;br&gt;
It's done a lot of heavy lifting -- produced a lot of code, and did it very fast. &lt;/p&gt;

&lt;p&gt;If you know what you are doing (if you know what questions to ask Copilot in the right way) -- your effectiveness as a software engineer will go through the roof. Some unintended findings -- sometimes, you ask Copilot a question, but it kind of knows where you are going with it, so it does a lot more than you ask. For instance, I asked to apply a dark mode styling for a particular card which it missed on one screen, and it figured out that the rest of the cards need to have the same approach applied -- I didn't complained and accepted the change. &lt;/p&gt;

&lt;p&gt;At this point, I ran out of steam -- going to sleep, and will release all these changes tomorrow night after work. &lt;/p&gt;

&lt;p&gt;P.S. After proof reading this post, I realized -- one really has to keep the architecture clean (and simple) in order for Gen AI to be able to help effectively. GenAI praised this project highly, which was very flattering — all the prior decisions on the project helped to implement non trivial tasks super easily in just one session. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Before vs. After: How AI ‘Vibe-Coded’ a Mobile App Makeover That Defies Expectations!</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Sun, 15 Jun 2025 19:04:58 +0000</pubDate>
      <link>https://forem.com/dmitryame/vibe-coding-ux-improvements-for-mobile-app-before-and-after-side-by-side-comparison-h3j</link>
      <guid>https://forem.com/dmitryame/vibe-coding-ux-improvements-for-mobile-app-before-and-after-side-by-side-comparison-h3j</guid>
      <description>&lt;h1&gt;
  
  
  Claude 4.0 Did in 3 Hours What Would’ve Taken Me Months
&lt;/h1&gt;

&lt;p&gt;As previously stated, UI/UX is not my forte. So, today we will try to use the power of Gen AI to apply facelift on existing react-native project. &lt;/p&gt;

&lt;p&gt;In the previous post we've looked at different AI models available in github copilot in Agent mode &lt;a href="https://dev.to/dmitryame/design-smarter-testing-top-llms-for-mobile-interface-optimization-k89"&gt;https://dev.to/dmitryame/design-smarter-testing-top-llms-for-mobile-interface-optimization-k89&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on that post, the best model which will work for us is Claude 4.0.&lt;/p&gt;

&lt;p&gt;It took two iterations -- about 3 hours combined, 22 commits total. In the first iteration I hit the rate limit, so had no choice but to punt it to the next day. Still, the amount of work that I was able to accomplish even before I hit the limit is staggering. What I was able to complete in few hours would usually take me weeks or even months. &lt;/p&gt;

&lt;p&gt;Unlike in the previous post, where I gave different Models a high level prompt, asking to make general UI improvements, this time I went one component at a time and was very specific. I will not be sharing the details of my interactions with the Agent (it would be overwhelming) -- I will be sharing only the end results 2 screens (before and after) with some brief notes, and it will be self explanatory. &lt;/p&gt;

&lt;h1&gt;
  
  
  PhotosList component (landing screen)
&lt;/h1&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%2F4jfbrgebie27prrbt4rg.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%2F4jfbrgebie27prrbt4rg.png" alt=" " width="800" height="854"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  improvements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;top nav bar&lt;/li&gt;
&lt;li&gt;thumbnails

&lt;ul&gt;
&lt;li&gt;more rounder corners&lt;/li&gt;
&lt;li&gt; redesigned indicator for video&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;footer redesign

&lt;ul&gt;
&lt;li&gt;more consistent&lt;/li&gt;
&lt;li&gt;modern looking buttons&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  animation of the top nav bar (hiding text labels on scroll)
&lt;/h2&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%2Fyqtl8ln3jc6ijmc087cp.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%2Fyqtl8ln3jc6ijmc087cp.png" alt=" " width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Drawer navigation (hamburger menu)
&lt;/h1&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%2Fgyo04socr8sw0jokxfnv.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%2Fgyo04socr8sw0jokxfnv.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Starred Photos (empty list)
&lt;/h1&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%2F3ho0p1brwxlpe8u972pr.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%2F3ho0p1brwxlpe8u972pr.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Search Photos (empty list)
&lt;/h1&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%2Fpm1w3qq38089o6po5odl.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%2Fpm1w3qq38089o6po5odl.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  improvements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;input text box&lt;/li&gt;
&lt;li&gt;action button&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Thumbs with comments on Starred and Search Photos
&lt;/h1&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%2Fszk4td91ug19c0r7ishk.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%2Fszk4td91ug19c0r7ishk.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Photo Details screen
&lt;/h1&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%2Fkb60u76fveyu5mlhwyqs.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%2Fkb60u76fveyu5mlhwyqs.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  improvements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;dark theme&lt;/li&gt;
&lt;li&gt;top nav bar&lt;/li&gt;
&lt;li&gt;more modern card view approach for components:

&lt;ul&gt;
&lt;li&gt;comments&lt;/li&gt;
&lt;li&gt;AI recognized tag, text, moderation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;label based design for the individual tags&lt;/li&gt;

&lt;li&gt;better colors&lt;/li&gt;

&lt;li&gt;bottom footer redesign&lt;/li&gt;

&lt;/ul&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%2F9l84vlfj46kjwy6ylznb.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%2F9l84vlfj46kjwy6ylznb.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Add comments screen
&lt;/h1&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%2Fxs90qhop7ze6p0opbokd.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%2Fxs90qhop7ze6p0opbokd.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Zoom View Screen
&lt;/h1&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%2Ffp2a3b86z9o3y37xwbac.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%2Ffp2a3b86z9o3y37xwbac.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Overall Tablet improvements
&lt;/h1&gt;

&lt;h3&gt;
  
  
  photos list
&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%2F7t5s2816wpelfjjla8av.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%2F7t5s2816wpelfjjla8av.png" alt=" " width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  detailed photo view
&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%2Fc7m6fwhqn18uin3a3rp1.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%2Fc7m6fwhqn18uin3a3rp1.png" alt=" " width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Works on Android too (love react-native)
&lt;/h1&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%2Ffvz4rqvw8lip6g6wywgi.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%2Ffvz4rqvw8lip6g6wywgi.png" alt=" " width="800" height="1718"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fgo77tnjwnmchlqlprwb4.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%2Fgo77tnjwnmchlqlprwb4.png" alt=" " width="800" height="1718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Extras bonus
&lt;/h1&gt;

&lt;p&gt;I noticed, as the Agent was making improvements, in one of the commits it added support for Haptic feedback -- I will take it as a free bonus. Unfortunately not able to test it until the changes rolled out to prod, but hopefully they just work. &lt;/p&gt;

&lt;h1&gt;
  
  
  Using the right LLM for the right purpose
&lt;/h1&gt;

&lt;p&gt;After making all these cool updates, releasing the mobile app to the app store is still task on its own. Especially after all these UI changes -- need to update the app store screenshots. I don't even have the right tools for slicing and dicing the images, but I do have ChatGPT, let's give it a try. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; generate iTunes store screen shot image, use first image as the the most up to date screenshot and the second image as a guidelines for design, the dimensions should be 1242 × 2688px&lt;br&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%2F0gr7cnw26io0h2vxy2rr.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%2F0gr7cnw26io0h2vxy2rr.png" alt=" " width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chat GPT:&lt;/strong&gt;&lt;br&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%2Fokhlnke3dxi7aa68u6h8.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%2Fokhlnke3dxi7aa68u6h8.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; take the mobile phone content area only from the first image and apply it to the second image in the correct place of the phone screen&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chat GPT:&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fsezj29vc746thl8ajqfz.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%2Fsezj29vc746thl8ajqfz.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; you lost the headline from the second photo, apply it to the results&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chat GPT:&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2F7qwbb4dqojbmc271ev02.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%2F7qwbb4dqojbmc271ev02.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
Well, not exactly what I was hoping for, and kind of funny results, oh well, some day soon I'm sure it will be able to do it correctly. &lt;/p&gt;

&lt;h2&gt;
  
  
  Let's try the same prompt with Claude 4.0 model in copilot Agent mode.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt;&lt;br&gt;
generate iTunes store screen shot image, the output dimensions should be 1242 × 2688px, apply the screenshot which is #iphone1_1.png to the phone screen image on the #iphone1.png maintaining the proper expected screen size, position, rotation, preserve the headline on the top of the iphone1.png, final image output file name iphone1_2.png&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Sorry, your request failed. Please try again. Request id: 4bd0b681-6758-4471-9c91-9c17a0231be9&lt;/p&gt;

&lt;p&gt;Reason: Please check your firewall rules and network connection then try again. Error Code: net::ERR_HTTP2_PROTOCOL_ERROR.&lt;/p&gt;

&lt;p&gt;and it fails today... it probably ran out of limit for the day... will have to try again tomorrow... stay tuned for more updates.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Design Smarter: Testing Top LLMs for Mobile UI Optimization</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Sun, 15 Jun 2025 02:11:19 +0000</pubDate>
      <link>https://forem.com/dmitryame/design-smarter-testing-top-llms-for-mobile-interface-optimization-k89</link>
      <guid>https://forem.com/dmitryame/design-smarter-testing-top-llms-for-mobile-interface-optimization-k89</guid>
      <description>&lt;h1&gt;
  
  
  Can Gen AI Fix My Engineer-Looking UI?
&lt;/h1&gt;

&lt;p&gt;My friends often comment on  the software I write, that it looks like it was designed by a software engineer -- I take it as a compliment. But, there is certain truth to it -- I do not have a designers edge when it comes to creating beautiful looking UI.&lt;/p&gt;

&lt;p&gt;Gen AI for developers, like me, could be a game changer. And this is the assumption we are about to verify. Here is the prompt: &lt;strong&gt;"make UX improvements of the landing screen to make it more modern"&lt;/strong&gt;. We will give this prompt to different models available in github copilot, and will document the results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what  was it able to accomplish (screenshot)&lt;/li&gt;
&lt;li&gt;how long it took&lt;/li&gt;
&lt;li&gt;was it able to complete the task from the first attempt, or there were additional intermittent prompts to make it work.&lt;/li&gt;
&lt;li&gt;overall rating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For every iteration, we will wipe out the history, hard reset the git branch, and will start new session in the Agent mode with the different LLM. The prompt we will be using is purposefully somewhat high level -- we do want to push the limits to the edge and see what happens.&lt;/p&gt;

&lt;h1&gt;
  
  
  Here is the application we will be working on today:
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/echowaves/WiSaw" rel="noopener noreferrer"&gt;https://github.com/echowaves/WiSaw&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is our starting point:&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%2Fy5j91a8apx9unxpok93w.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%2Fy5j91a8apx9unxpok93w.png" alt="Image description" width="800" height="1400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The UI of this app has not changed for nearly 2 years, it was never designed by a professional, it's fully functional, but feels slightly out of date. &lt;/p&gt;

&lt;h2&gt;
  
  
  GPT 4.0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; make UX improvements of the landing screen to make it more modern&lt;br&gt;
&lt;strong&gt;copilot:&lt;/strong&gt; Unable to resolve "expo-linear-gradient" from "src/screens/PhotosList/index.js&lt;br&gt;
&lt;strong&gt;prompt:&lt;/strong&gt; Unable to resolve "expo-linear-gradient" from "src/screens/PhotosList/index.js&lt;/p&gt;

&lt;p&gt;after it stopped processing  ...&lt;br&gt;
&lt;strong&gt;prompt:&lt;/strong&gt; I do not see any visible differences to the way the screen looks&lt;/p&gt;

&lt;p&gt;It spent less than a minute and made no changes other than empty state, which I'm not even testing for, nor care about much.&lt;br&gt;
Little disappointing. Let's move on to the next model. &lt;/p&gt;

&lt;h3&gt;
  
  
  how long it took
&lt;/h3&gt;

&lt;p&gt;about 5 minutes total&lt;/p&gt;

&lt;h3&gt;
  
  
  was it able to complete the task from the first attempt, or there were additional intermittent prompts to make it work.
&lt;/h3&gt;

&lt;p&gt;not really &lt;/p&gt;

&lt;h3&gt;
  
  
  overall rating
&lt;/h3&gt;

&lt;p&gt;disappointing&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT 4.1
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; make UX improvements of the landing screen to make it more modern&lt;br&gt;
&lt;strong&gt;copilot:&lt;/strong&gt;&lt;br&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%2Fcqiqds98ygg63bkwg3ys.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%2Fcqiqds98ygg63bkwg3ys.png" alt="Image description" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;10 minutes later it got stuck on installing &lt;em&gt;expo-linear-gradient&lt;/em&gt; npm&lt;br&gt;
Let's kick it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; skip the install expo-linear-gradient step, it's already done, continue to the next step&lt;br&gt;
&lt;strong&gt;copilot:&lt;/strong&gt; The landing screen has been modernized with a gradient background, bold app title, subtitle, modern card/button styles, and a prominent call-to-action. All formatting and lint issues are resolved. Your landing screen now has a more modern and visually appealing UX.&lt;br&gt;
Would you like to preview or further customize any specific elements?&lt;br&gt;
&lt;strong&gt;prompt:&lt;/strong&gt; yes&lt;br&gt;
&lt;strong&gt;copilot:&lt;/strong&gt; Great! Please let me know which specific elements or aspects of the landing screen you’d like to further customize or preview (e.g., colors, button style, icon, layout, animation, or text). I can make additional UX/UI improvements or adjustments as you wish.&lt;/p&gt;

&lt;h3&gt;
  
  
  what  was it able to accomplish (screenshot)
&lt;/h3&gt;

&lt;p&gt;No point posting the original screenshot -- no visible differences at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  how long it took
&lt;/h3&gt;

&lt;p&gt;about 15 minutes total&lt;/p&gt;

&lt;h3&gt;
  
  
  was it able to complete the task from the first attempt, or there were additional intermittent prompts to make it work.
&lt;/h3&gt;

&lt;p&gt;not really &lt;/p&gt;

&lt;h3&gt;
  
  
  overall rating
&lt;/h3&gt;

&lt;p&gt;disappointing &lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini 2.5 (preview)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; make UX improvements of the landing screen to make it more modern&lt;/p&gt;

&lt;h3&gt;
  
  
  what  was it able to accomplish (screenshot)
&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%2F2sac2a62447lh6je2vb4.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%2F2sac2a62447lh6je2vb4.png" alt="Image description" width="800" height="1400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;the bottom buttons look more rounder and better aligned, other than that -- no significant differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  how long it took
&lt;/h3&gt;

&lt;p&gt;about 5 minutes&lt;/p&gt;

&lt;h3&gt;
  
  
  was it able to complete the task from the first attempt, or there were additional intermittent prompts to make it work.
&lt;/h3&gt;

&lt;p&gt;yes&lt;/p&gt;

&lt;h3&gt;
  
  
  overall rating
&lt;/h3&gt;

&lt;p&gt;3 out of 5&lt;/p&gt;

&lt;p&gt;It was able to complete the task, but the changes were so minor, almost unnoticeable and not worth talking about. &lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Sonnet 4 (preview)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;prompt:&lt;/strong&gt; make UX improvements of the landing screen to make it more modern&lt;br&gt;
&lt;strong&gt;copilot:&lt;/strong&gt; &lt;br&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%2F2ufu0gav0ou2agmqe9rb.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%2F2ufu0gav0ou2agmqe9rb.png" alt="Image description" width="800" height="801"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;7 minutes later:&lt;br&gt;
&lt;strong&gt;copilot:&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2F90mqns4jwqyjjkh3z4si.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%2F90mqns4jwqyjjkh3z4si.png" alt="Image description" width="800" height="1310"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fg51nm2q8wzbgfd61qz4l.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%2Fg51nm2q8wzbgfd61qz4l.png" alt="Image description" width="800" height="1254"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fm1h94mgsrdz620npln4a.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%2Fm1h94mgsrdz620npln4a.png" alt="Image description" width="800" height="1331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  what  was it able to accomplish (screenshot)
&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%2Fehka43wq3gn1lwracnsk.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%2Fehka43wq3gn1lwracnsk.png" alt="Image description" width="800" height="1400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  how long it took
&lt;/h3&gt;

&lt;p&gt;About 7 minutes&lt;/p&gt;

&lt;h3&gt;
  
  
  was it able to complete the task from the first attempt, or there were additional intermittent prompts to make it work.
&lt;/h3&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;h3&gt;
  
  
  overall rating
&lt;/h3&gt;

&lt;p&gt;4.5 stars&lt;br&gt;
It was able to produce something far much better/modern looking with little (close to non) guidance. The back and forth dialog with the Agent while vibe coding was very meaningful, it was easy to understand what it was trying to accomplish, it took direction really well and dug deep in the projects code, trying to understand what should be done to fulfill the prompt the best possible way -- impressive. &lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the models (screenshot from ChatGPT generated summary):
&lt;/h2&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%2Fzbshj4fo3354jjkgh6al.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%2Fzbshj4fo3354jjkgh6al.png" alt="Image description" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clearly, Claude 4  stands out — it was able to modernize the landing screen with little direction. Gemini did okay. GPT-4.x models were disappointing -- they underperformed in this specific context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; 🚀 GenAI for UI may not replace designers, but it’s already a powerful assistant — especially when paired with the right model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask GPT to re-design modern looking screenshot (no coding)
&lt;/h2&gt;

&lt;p&gt;What if we ask ChatGPT to generate a visual screenshot with modern UI/UX improvements based on the current screenshot of the app. Will it be able to do it? Let's give it a try:&lt;br&gt;
&lt;strong&gt;prompt:&lt;/strong&gt; Generate a screenshot image, which represents UX improvements of the screenshot attached reflecting modern trends and user expectations&lt;br&gt;
&lt;strong&gt;chat GPT:&lt;/strong&gt;&lt;br&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%2Faagp9c2kjjve62wr5oje.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%2Faagp9c2kjjve62wr5oje.png" alt="Image description" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interesting. It was able to come up with some interesting ideas for visual layout improvements. I always wanted to implement the brick layout for this app, still have to figure it out some day. &lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: As the last step of the exercise
&lt;/h2&gt;

&lt;p&gt;I need to generate a cool looking head image for this post, let's try to use Chat GPT for that and see how well it performs:&lt;br&gt;
&lt;strong&gt;prompt:&lt;/strong&gt; &lt;br&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%2F5g01carv638nwaftxd0u.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%2F5g01carv638nwaftxd0u.png" alt="Image description" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It took few hours, and multiple attempts -- the best image I've got from &lt;strong&gt;Chat GPT&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fmo5hp1wrvdlencxp76jv.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%2Fmo5hp1wrvdlencxp76jv.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I gave various prompts trying to realign the text -- no much success, perhaps I'm still learning the proper techniques. &lt;/p&gt;

&lt;p&gt;Lets try something different... &lt;br&gt;
&lt;strong&gt;prompt:&lt;/strong&gt; "create a picture 1000x420 pixels which shows comparison of the mobile apps design on different devices, screens, iOS, android, Tablets etc..."&lt;br&gt;
&lt;strong&gt;chat GPT:&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fljj5g6077lso72sactfr.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%2Fljj5g6077lso72sactfr.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not ideal, but will work. As I previously stated, I'm a software engineers, not a UI designer. This less than perfect image does the job for me.&lt;/p&gt;

&lt;p&gt;In the next post we will use the winner model &lt;strong&gt;Claude 4.0&lt;/strong&gt;, and, in vibe coding session, we will try to address one component at a time, and compare before and after results. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/dmitryame/vibe-coding-ux-improvements-for-mobile-app-before-and-after-side-by-side-comparison-h3j"&gt;https://dev.to/dmitryame/vibe-coding-ux-improvements-for-mobile-app-before-and-after-side-by-side-comparison-h3j&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding...&lt;/p&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Redirect www to ROOT in AWS RT53</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Sun, 25 May 2025 22:17:59 +0000</pubDate>
      <link>https://forem.com/dmitryame/redirect-www-to-root-in-aws-1ee4</link>
      <guid>https://forem.com/dmitryame/redirect-www-to-root-in-aws-1ee4</guid>
      <description>&lt;h1&gt;
  
  
  Why to redirect to ROOT?
&lt;/h1&gt;

&lt;p&gt;Don't believe me, just type &lt;code&gt;is www an anachronism&lt;/code&gt; in Google search. Most likely you will get response similar to the following:&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%2Fn7erckiqfemfeki3d8c9.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%2Fn7erckiqfemfeki3d8c9.png" alt="Image description" width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What's going on in AWS.
&lt;/h1&gt;

&lt;p&gt;I recently migrated my domains from Godaddy to RT53, and the process was not as straight forward as one would hope. &lt;br&gt;
Route 53 offers extensive options for referencing AWS resources at the domain level, enabling better integration and making the effort worthwhile.&lt;/p&gt;

&lt;p&gt;Most of domain registries (like Godaddy) offer convenient user interface for allowing customers to forward domains. There is no W3C standard which regulates how DNS registry providers implement domain forwarding. So it's totally up to the individual providers how to implement the forwarding. &lt;/p&gt;

&lt;p&gt;Unlike most of the domain registries providers, RT53 does not offer the domain forwarding option out of the box. If you read documentation, it will tell you that defining CNAME record in RT53 is all you have to do. Well, it just does not work that way (based on my experience, feel free to post in comments if I'm wrong -- always looking for learn something new). The only way I could make it work,  was by adding simple Edge function, which intercepts all requests to my web app, and returns the 301 (Moved Permanently) response programmatically. &lt;/p&gt;
&lt;h1&gt;
  
  
  Things do not work in RT53 the same way as in ... Godaddy.
&lt;/h1&gt;

&lt;p&gt;Just to prove my point, here is the &lt;code&gt;CNAME&lt;/code&gt; record I have in my hosted zone for &lt;a href="https://wwww.wisaw.com" rel="noopener noreferrer"&gt;https://wwww.wisaw.com&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fnn0dg5xfrjd83vhrxtbk.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%2Fnn0dg5xfrjd83vhrxtbk.png" alt="Image description" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, if you click the link above, it will not resolve to anything. &lt;br&gt;
Let's see what is the easiest way to implement the alternative solution.&lt;/p&gt;
&lt;h1&gt;
  
  
  Wiring things up for the Edge function.
&lt;/h1&gt;

&lt;p&gt;In your CDK stack, define the Edge function 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redirectLambdaEdgeFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="c1"&gt;// new lambda.Function( // trying to define it as an Lambda@Edge function&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EdgeFunction&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;deployEnv&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;_redirectLambdaEdgeFunction`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_22_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../lambda-fns/lambdas/redirectLambdaEdgeFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="c1"&gt;// code: lambda.Code.fromAsset('lambda-fns/lambdas.zip'),&lt;/span&gt;
            &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="c1"&gt;// insightsVersion,&lt;/span&gt;
            &lt;span class="nx"&gt;logRetention&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;p&gt;Assuming you are hosting your single page app on &lt;code&gt;S3&lt;/code&gt; bucket with &lt;code&gt;CloudFront&lt;/code&gt; distro (read how to do it here: &lt;a href="https://dev.to/dmitryame/serving-static-content-from-s3-bucket-195p"&gt;https://dev.to/dmitryame/serving-static-content-from-s3-bucket-195p&lt;/a&gt; ), add &lt;code&gt;edgeLambdas&lt;/code&gt; section to the &lt;code&gt;defaultBehavior&lt;/code&gt; of your &lt;code&gt;CloudFront distribution&lt;/code&gt; 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="c1"&gt;// Create the CloudFront distribution with S3 as an origin&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Distribution&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wisaw-distro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;priceClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PriceClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRICE_CLASS_100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;defaultBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront_origins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3BucketOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withOriginAccessControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webAppBucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;compress&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="na"&gt;cachePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;basicCachePolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;originRequestPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;allForwardPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;viewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_TO_HTTPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Add this line&lt;/span&gt;
          &lt;span class="na"&gt;edgeLambdas&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="na"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LambdaEdgeEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWER_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;functionVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redirectLambdaEdgeFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;includeBody&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="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;h1&gt;
  
  
  The Edge function implementation.
&lt;/h1&gt;

&lt;p&gt;And here is the &lt;code&gt;Edge&lt;/code&gt; function implementation:&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="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// CloudFront headers are in format: { "host": [{"key": "Host", "value": "www.wisaw.com"}] }&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&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="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www.wisaw.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Build the new URL, preserving query string if present&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://wisaw.com&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querystring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querystring&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;301&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Moved Permanently&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Location&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redirectUrl&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty straight forward and self explanatory. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intercept all requests&lt;/li&gt;
&lt;li&gt;If the requested host matches &lt;code&gt;www.wisaw.com&lt;/code&gt; -- prepare the response with the redirectUrl which strips prefixing &lt;code&gt;www&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;return this response with status &lt;code&gt;301&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After redeploying your CDK stack -- things are expected to just work. Well, not quite yet. Keep reading.&lt;/p&gt;

&lt;h1&gt;
  
  
  Still need a CNAME
&lt;/h1&gt;

&lt;p&gt;You still need a CNAME record in RT53 in addition to the Lambda Edge function to make it work correctly. If you have one but not the other -- it will not work. So, you need both. But adding a CNAME is easy. Here is what I've got:&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%2Fyhpn7oq8bsu6p6xpo0aq.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%2Fyhpn7oq8bsu6p6xpo0aq.png" alt="Image description" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Final touches.
&lt;/h1&gt;

&lt;p&gt;Just to make things perfect, you will also want to make sure all your &lt;code&gt;http&lt;/code&gt; request redirect to &lt;code&gt;https&lt;/code&gt;. The lambda function will already do it for you if you are requesting the &lt;code&gt;www&lt;/code&gt; subdomain. But what if you are hitting &lt;a href="http://wisaw.com" rel="noopener noreferrer"&gt;http://wisaw.com&lt;/a&gt; ? In the &lt;code&gt;CloudFront&lt;/code&gt; definition of your &lt;code&gt;CDK&lt;/code&gt; stack, there is one line of code which defines this behaviour:&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="nx"&gt;viewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_TO_HTTPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Add this line&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's all. Thanks for reading.&lt;/p&gt;

&lt;p&gt;The complete code for this post --&amp;gt;  &lt;a href="https://github.com/echowaves/WiSaw.cdk" rel="noopener noreferrer"&gt;https://github.com/echowaves/WiSaw.cdk&lt;/a&gt;&lt;br&gt;
The web app hosted on S3 bucked with properly defined redirects is &lt;a href="https://wisaw.com" rel="noopener noreferrer"&gt;https://wisaw.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy Hacking.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>rt53</category>
    </item>
    <item>
      <title>Serving static content from S3 bucket</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Sat, 24 May 2025 20:38:46 +0000</pubDate>
      <link>https://forem.com/dmitryame/serving-static-content-from-s3-bucket-195p</link>
      <guid>https://forem.com/dmitryame/serving-static-content-from-s3-bucket-195p</guid>
      <description>&lt;h1&gt;
  
  
  Need for static content
&lt;/h1&gt;

&lt;p&gt;Static content remains the fastest way to deliver information to browsers. As a result, many vendors now focus on optimizing it through geographic distribution, caching, CDNs, and more. Amazon is a key player in this space, offering robust solutions for efficient static content delivery.&lt;/p&gt;

&lt;h1&gt;
  
  
  S3 bucket, brute force approach
&lt;/h1&gt;

&lt;p&gt;At the core of AWS’s storage technology is the S3 bucket, a natural choice for hosting static content and delivering it directly to the browser.&lt;br&gt;
My personal preference for creating and managing s3 buckets for  static content via AWS console, manually, without scripting it in CDK -- just gives me a bit more peace of mind for bucket life cycle (when it's created and deleted). &lt;/p&gt;

&lt;p&gt;To get started, sign into your AWS console, and go to the S3 bucket section. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create a bucket with the name of your host. In my case it will be &lt;code&gt;wisaw.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;in the &lt;code&gt;properties&lt;/code&gt; tab, enable it for &lt;code&gt;Static website hosting&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;in the &lt;code&gt;permissions&lt;/code&gt; tab, edit &lt;code&gt;Block public access&lt;/code&gt; and  make sure all public access is enabled by unchecking all the check boxes.&lt;/li&gt;
&lt;li&gt;in &lt;code&gt;RT53&lt;/code&gt; section of the console, create or edit record type &lt;code&gt;A&lt;/code&gt; for your domain (wisaw.com for me), and configure it as an alias to the S3 bucket (should be available in the dropdown list). &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This was easy. You site should be working now, and the content delivered straight from &lt;code&gt;S3&lt;/code&gt; to your browser. However, I'm really not comfortable leaving the &lt;code&gt;S3&lt;/code&gt; bucket wide open for public access -- there has to be a better, more secure way. &lt;/p&gt;
&lt;h1&gt;
  
  
  Shooting for perfection -- closing all public access to S3 bucket
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Go ahead and disable all public access to your bucket in console.
&lt;/h2&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%2F9bctjv966nwevoalqi0p.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%2F9bctjv966nwevoalqi0p.png" alt="Image description" width="800" height="753"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we create &lt;code&gt;CloudFront&lt;/code&gt; distribution in our CDK script, we need to get a references to few additional resources.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reference S3 bucket:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webAppBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromBucketName&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="s2"&gt;`wisaw.com`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`wisaw.com`&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;
  
  
  Few more things needed for &lt;code&gt;CloudFront&lt;/code&gt;:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;      &lt;span class="c1"&gt;// Use the ACM certificate&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCertificateArn&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my_cert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arn:aws:acm:us-east-1:963958500685:certificate/cf8703c9-9c1b-4405-bc10-a0c3287ebb7e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// Create cache policies&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicCachePolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CachePolicy&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BasicCachePolicy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;defaultTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;minTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;maxTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;enableAcceptEncodingGzip&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="na"&gt;enableAcceptEncodingBrotli&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="na"&gt;queryStringBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CacheQueryStringBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;cookieBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CacheCookieBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Create origin request policy that forwards all cookies and query strings&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allForwardPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OriginRequestPolicy&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AllForwardPolicy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;cookieBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OriginRequestCookieBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;queryStringBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OriginRequestQueryStringBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;headerBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OriginRequestHeaderBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none&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;
  
  
  And now, Create a new &lt;code&gt;CloudFront&lt;/code&gt; distribution in your CDK script:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;     &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Distribution&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wisaw-distro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;priceClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PriceClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRICE_CLASS_100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;defaultBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront_origins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3BucketOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withOriginAccessControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webAppBucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;compress&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="na"&gt;cachePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;basicCachePolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;originRequestPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;allForwardPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;viewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_TO_HTTPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Add this line&lt;/span&gt;
          &lt;span class="na"&gt;edgeLambdas&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="na"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LambdaEdgeEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWER_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;functionVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redirectLambdaEdgeFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;includeBody&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="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
       &lt;span class="na"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;domainNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;www.wisaw.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wisaw.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;minimumProtocolVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecurityPolicyProtocol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TLS_V1_2_2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;errorResponses&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="na"&gt;httpStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;responseHttpStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;responsePagePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;httpStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;responseHttpStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;responsePagePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Output the Distribution ID to use in the OAC bucket policy&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnOutput&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CloudFrontDistributionId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distributionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use this Distribution ID in the OAC bucket policy for wisaw.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Final steps
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Add an alias &lt;code&gt;A&lt;/code&gt; record to your Rt53 hosting zone referencing the &lt;code&gt;CloudFront&lt;/code&gt; distro:
&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%2Frvboow9o9y8cq12vociy.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%2Frvboow9o9y8cq12vociy.png" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Add bucket policy in the &lt;code&gt;permissions&lt;/code&gt; tab for your s3 bucket:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::wisaw.com/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::963958500685:distribution/E37CLMKVO7KBVG"
                }
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can get the &lt;code&gt;CloudFront&lt;/code&gt; ARN from the aws console. &lt;/p&gt;
&lt;h1&gt;
  
  
  Sometimes there is still a need to open public access... briefly.
&lt;/h1&gt;

&lt;p&gt;The static application I host is a &lt;code&gt;React.js&lt;/code&gt; single page app. The static content of the site is not changing frequently. But, when I need to redeploy the app (synchronize the bucket), I go to the console and enable public access for few minutes. Just don't forget to disable it again once you are done with your updates. I'm sure there is a better way, feel free to add a comment and share some cool techniques how to do it. &lt;/p&gt;
&lt;h1&gt;
  
  
  What if things don't work.
&lt;/h1&gt;

&lt;p&gt;The example described here is a basic use case which should get you going. In real life your CDK script is expected to be a lot more complex, depending on your application. For instance, my app has few &lt;code&gt;Lambda Edge&lt;/code&gt; functions which write to the same bucket. These function do not need public access, since they are accessing your bucket from within your account VPC. &lt;br&gt;
Usually you simply have to add a following line to you CDK:&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="c1"&gt;// Grant the Lambda function permissions to read and write to the S3 bucket&lt;/span&gt;
      &lt;span class="nx"&gt;webAppBucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantReadWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generateSiteMap_LambdaFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While closing public access to my &lt;code&gt;S3&lt;/code&gt; bucket, I ran into a situation that my &lt;code&gt;generateSiteMap_LambdaFunction&lt;/code&gt; was not able to update the sitemap.xml stored in the bucket and was getting rejected access. I have researched tons of documentation on the web and spent few days trying different suggestions like updating  bucket and function policies, configuring AIM access control etc... It turned out a silly bug that was driving me towards these complex solutions. The function writing to &lt;code&gt;S3&lt;/code&gt; bucket was passing one of the parameters:&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="nx"&gt;ACL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public-read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since all the public access was disabled -- the function was failing. Removing that parameter fixed the problem. I was able to solve this fairly easily while vibe coding with &lt;code&gt;Github Copilot&lt;/code&gt;. So, if you feel stuck and find yourself having to implement complex solutions for simple use cases -- there is probably a simpler answer. &lt;/p&gt;

&lt;h1&gt;
  
  
  Better safe than sorry.
&lt;/h1&gt;

&lt;p&gt;Even if you think you have closed all public access to all your buckets, it's a good idea to still periodically check. AWS offers out of the box a tool, called &lt;code&gt;IAM Access Analyzer for S3&lt;/code&gt;.&lt;br&gt;
Running it every once in a while is probably not a bad idea:&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%2Fbf2mstar65yy0yhoa958.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%2Fbf2mstar65yy0yhoa958.png" alt="Image description" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete code for this post --&amp;gt; &lt;a href="https://github.com/echowaves/WiSaw.cdk" rel="noopener noreferrer"&gt;https://github.com/echowaves/WiSaw.cdk&lt;/a&gt;&lt;br&gt;
The web app hosted on S3 bucked --&amp;gt; &lt;a href="https://wisaw.com" rel="noopener noreferrer"&gt;https://wisaw.com&lt;/a&gt;&lt;br&gt;
Another post in this series talking about redirects in AWS --&amp;gt; &lt;a href="https://dev.to/dmitryame/redirect-www-to-root-in-aws-1ee4"&gt;https://dev.to/dmitryame/redirect-www-to-root-in-aws-1ee4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy Hacking.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>s3</category>
    </item>
    <item>
      <title>Make Coding Fun Again</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Sat, 26 Apr 2025 20:46:36 +0000</pubDate>
      <link>https://forem.com/dmitryame/make-coding-fun-again-1p2a</link>
      <guid>https://forem.com/dmitryame/make-coding-fun-again-1p2a</guid>
      <description>&lt;h1&gt;
  
  
  The Hidden Cost of a Career in Tech: Losing Motivation Over Time
&lt;/h1&gt;

&lt;p&gt;During the day, I serve as an engineering manager. In the evenings, when I have the energy, I dedicate time to exploring software side projects. I don't get many opportunities to work on what I want at work.  The best way to keep up with the industry trends -- is to work on emerging tech, and I've done it pretty much through out my entire software engineering career. I witnessed the inception of the Internet, I contributed to open source, I published  NPM modules which got pretty good downloads, and I even had an opportunity to work on my own block chain implementation from scratch.  So "I've been there, I've done that". Though, there is a little side effect which accumulates over the years -- with time it's getting more difficult to find motivation to open some old code and keep refining it. This is out of the questions -- hard.  And I know, I'm not the only one who feels that way.&lt;/p&gt;

&lt;h1&gt;
  
  
  Time to get a foot wet with GenAI
&lt;/h1&gt;

&lt;p&gt;While recently spending a week in sunny LA, I finally had an opportunity to sit down and think about the next cool side project --  something that would really be a game changer for me. I adhere to East coast work hours. Pushing myself to get up at 5:30 AM local time to be able to remotely attend my first meeting at 9:00 AM EST was hard. But... when 2 pm hits in LA, I'm free to do what ever I want. Perhaps it's time to give GenAI for coding a try. &lt;/p&gt;

&lt;h1&gt;
  
  
  Using GenAI for coding is a game changer
&lt;/h1&gt;

&lt;p&gt;As an emerging tech, GenAI was on my radar for the past year (way too long in web years). From all the buzz on the web I started to think about GenAI as an incredibly powerful crutch for writing software. This post describes my experience using Github Copilot, which was an eye opener --  after performing this little experiment I now think of GenAI for coding as a super powerful Exoskeleton.  &lt;/p&gt;

&lt;h1&gt;
  
  
  How to get started with GenAI for coding
&lt;/h1&gt;

&lt;p&gt;There are a lot of tools and options available. Github Copilot has a free version which makes it a natural pick for most devs. First, I found a &lt;a href="https://www.udemy.com/course/github-copilot" rel="noopener noreferrer"&gt;Udemy class&lt;/a&gt; with best reviews, and, started watching... This online class is only 7 hours long. When it comes to consuming video information, I do have short attention span -- watching the materials at 2x forces me to stay focused, and I was able to complete my initial training in 2 evenings without feeling overwhelmed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Giving a second life to my old code
&lt;/h1&gt;

&lt;p&gt;One of my oldest active projects is a photo sharing mobile app called WiSaw, which stands for "What I Saw". I also host a simple read-only web app &lt;a href="https://wisaw.com" rel="noopener noreferrer"&gt;https://wisaw.com&lt;/a&gt; , which is used mostly for establishing a promotional footprint on the web, so that people can find and download the mobile app. I have not touched this project in a while, perhaps it's a good time to start using new tools which were not available back then to give this project a little boost?&lt;/p&gt;

&lt;h1&gt;
  
  
  Asking ChatGPT for advice about improvements needed
&lt;/h1&gt;

&lt;p&gt;About a year ago I started talking to ChatGPT. &lt;br&gt;
&lt;strong&gt;Q:&lt;/strong&gt; &lt;em&gt;"what do internet users think of wisaw.com and what should be done to improve the perception".&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;A:&lt;/strong&gt; &lt;em&gt;"WiSaw offers a collection of free, authentic stock photos and videos. However, potential users should be aware of its moderate trust score and consider exercising caution when using the site."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After all, I never intended to make wisaw.com a real app, but, at this point, I felt, it was hurting the mobile app presence more than serving the originally intended purpose of promoting it. &lt;/p&gt;

&lt;p&gt;The next question to ChatGPT &lt;br&gt;
&lt;strong&gt;Q:&lt;/strong&gt; &lt;em&gt;"Hey ChatGPT, what can be done to improve the trust score for wisaw.com"&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;A:&lt;/strong&gt; "Improving the trust score of a platform like WiSaw involves addressing factors that contribute to user confidence and website credibility. Here are some actionable steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enhance Website Transparency 

&lt;ul&gt;
&lt;li&gt;About Page: Provide detailed information about the company, its mission, and the team behind the platform.&lt;/li&gt;
&lt;li&gt;Contact Information: Display clear and accessible contact details, including a physical address, email, and phone number.&lt;/li&gt;
&lt;li&gt;Terms and Policies: Ensure comprehensive and accessible Terms of Service, Privacy Policy, and copyright guidelines. ... (only shown the first 3 suggestion as the most relevant)."&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Defining the scope
&lt;/h1&gt;

&lt;p&gt;So, I have to add a top nav bar to the web app with 3 menu items. It's seemingly a simple task, right? Simple is not easy. It consists of a lot of moving parts and requires understanding of concepts like routing, page templating (headers, footers, etc...), it may require adding some additional dependencies. It's better be implemented as a responsive feature so it opens properly in any browser including mobile devices. More than trivial knowledge of HTML, JS, CSS, and how it all fits together is needed.  It requires a lot of boiler plate coding and applying copy/paste design pattern. I would not find working on these tasks particularly challenging (read interesting/satisfying) -- this little project seems like a perfect candidate hand off to GenAI.&lt;/p&gt;

&lt;p&gt;In order to get the best of Github Copilot as quickly as possible, highly recommend watching that &lt;a href="https://www.udemy.com/course/github-copilot" rel="noopener noreferrer"&gt;Udemy class&lt;/a&gt;. It will really help to jump start your experience and avoid wasting precious time. It will tell you about different ways of interacting with Copilot (Chat mode, vs. Inline Comments, vs. Agent mode). Different versions of VSCode support various  GenAI features and have access to different AI models -- you really need to know about all this to be proficient on day one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let Copilot do the coding
&lt;/h1&gt;

&lt;p&gt;After getting through initial dev environment setup, I started interacting with Copilot in Agent mode. Agent mode is like pair programing with GenAI. Going forward I will refer to my pair programming partner as simply "Copilot".&lt;/p&gt;

&lt;p&gt;First question I asked Copilot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add responsive nav bar with 3 menu items "About", "Contact Us", "Terms and Policies"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then I was just sitting and watching Copilot navigating around my project in real-time, opening the rights folders, updating the right files. Took about 2 minutes to produce fully functional code.   &lt;/p&gt;

&lt;p&gt;Here is a transcript of my interaction with Copilot (I'm posting it as a screenshots, just because it's so cool to see the real user experience):&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%2Fgj3xhnpwscuiiewh6z0d.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%2Fgj3xhnpwscuiiewh6z0d.png" alt="Image description" width="800" height="448"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fguciqcdredki44b4gekt.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%2Fguciqcdredki44b4gekt.png" alt="Image description" width="800" height="448"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fw67262x9gcfmol87xyyr.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%2Fw67262x9gcfmol87xyyr.png" alt="Image description" width="800" height="448"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fl0i2rv0et0ki04cc1x1q.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%2Fl0i2rv0et0ki04cc1x1q.png" alt="Image description" width="800" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ha, pretty cool! It was easy, it was quick, it was FUN!&lt;/p&gt;

&lt;h1&gt;
  
  
  What else can Copilot do?
&lt;/h1&gt;

&lt;p&gt;Here are few other things I did with the help of Copilot before publishing the changes to prod:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; &lt;em&gt;"add default imeplementation of the about page"&lt;/em&gt;&lt;br&gt;
Note that I even misspelled "imeplementation", but Copilot was able to understand what I'm asking.&lt;br&gt;
I then spent some time on fine crafting the content manually, which was the most time consuming task, but something that had to be done. &lt;/p&gt;

&lt;h1&gt;
  
  
  Even more tasks:
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; &lt;em&gt;"how to make href open in new tab"&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.amazonaws.com%2Fuploads%2Farticles%2Fa8spcd4pedhgnr7thp9g.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%2Fa8spcd4pedhgnr7thp9g.png" alt="Image description" width="800" height="275"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fle2hw8h1351tlihyrdl5.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%2Fle2hw8h1351tlihyrdl5.png" alt="Image description" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Sky is the limit -- shooting for perfection
&lt;/h1&gt;

&lt;p&gt;Since I'm at it anyways, why don't I try to really make my project perfect?&lt;br&gt;
&lt;strong&gt;Q:&lt;/strong&gt; &lt;em&gt;"fix all valnurebilities in this project"&lt;/em&gt;&lt;/p&gt;

&lt;p&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.amazonaws.com%2Fuploads%2Farticles%2Foyhpiw4gblymqr1tdaxj.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%2Foyhpiw4gblymqr1tdaxj.png" alt="Image description" width="800" height="336"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fow0xcrdtx6ettny1payb.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%2Fow0xcrdtx6ettny1payb.png" alt="Image description" width="800" height="314"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fkxun20e9fa473nu5bgql.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%2Fkxun20e9fa473nu5bgql.png" alt="Image description" width="800" height="332"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fctmw2v0z51has1zo0tqg.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%2Fctmw2v0z51has1zo0tqg.png" alt="Image description" width="800" height="332"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fzbbt1xi52nhhmc9xe18p.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%2Fzbbt1xi52nhhmc9xe18p.png" alt="Image description" width="800" height="332"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Figbbeikhmzbjzjmjv25e.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%2Figbbeikhmzbjzjmjv25e.png" alt="Image description" width="800" height="350"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fwsdsbzwq8dnaho79b751.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%2Fwsdsbzwq8dnaho79b751.png" alt="Image description" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wow, that was quite a lot. &lt;/p&gt;

&lt;h1&gt;
  
  
  Turning even more boring tasks into Fun
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; &lt;em&gt;"update the project to the latests versions of dependencies, use yarn instead of npm"&lt;/em&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Feyhdb9i0d7vl89hrue8j.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%2Feyhdb9i0d7vl89hrue8j.png" alt="Image description" width="800" height="421"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fn4cqx6twi0bsoogp816c.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%2Fn4cqx6twi0bsoogp816c.png" alt="Image description" width="800" height="421"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fd8lgb5lh0qmtb0vxpc5c.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%2Fd8lgb5lh0qmtb0vxpc5c.png" alt="Image description" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step was particularly cool, because it demonstrated a true power of the agent mode. In the agent mode Copilot was able to open a terminal window in VSCode, execute commands, parse the output from the results of running these commands, and prompt me (the user) what to do next! Very cool!&lt;/p&gt;

&lt;h1&gt;
  
  
  But that's not all
&lt;/h1&gt;

&lt;p&gt;I also have 2 published NPM modules which I have not opened in a while. &lt;br&gt;
With the help of Copilot I was able to update &lt;a href="https://www.npmjs.com/package/expo-cached-image" rel="noopener noreferrer"&gt;expo-cached-image&lt;/a&gt;  to the latest dependencies in no time, which is typically very laborious task.&lt;br&gt;
With the help of Copilot I was able to convert &lt;a href="https://www.npmjs.com/package/expo-storage" rel="noopener noreferrer"&gt;expo-storage&lt;/a&gt; to TypeScript. I personally do not like &lt;a href="https://dev.to/dmitryame/typescript-why-not-5dfg"&gt;TypeScript&lt;/a&gt;. I stated my career with strongly typed languages like C++ and Java, so introduction of TS felt like going back in time. But, since people want NPM's to not throw TS warnings, I had no problem converting my project, especially that I didn't have to do anything myself other than just sit and watch how Copilot does it for me.&lt;/p&gt;

&lt;h1&gt;
  
  
  Coding with Copilot was Fun
&lt;/h1&gt;

&lt;p&gt;Doing all these boring tasks manually would take me 2 days at least. With the help of Copilot it took 2 hours at most. Even if you add 3.5 hours watching the &lt;a href="https://www.udemy.com/course/github-copilot" rel="noopener noreferrer"&gt;Udemy class&lt;/a&gt;, 30 minutes setting up the dev environment,  the productivity boost was still amazing -- more than 100%! And it was FUN watching Copilot in real-time writing code and interacting back and forth with me. &lt;/p&gt;

&lt;p&gt;I would not expect every project to gain this much productivity with GenAI. My assumption is that the more experience you have as a software engineer -- the more gains you will be able to get from Copilot. &lt;/p&gt;

&lt;h1&gt;
  
  
  Since Copilot is so much better coder, should I worry about my job?
&lt;/h1&gt;

&lt;p&gt;Engineers are rightfully worrying these days "is my job safe?". GenAI is great, but, who is going to define the tasks and the scope of work? Who is going to watch over its shoulder for the quality of the code produced? How do we make sure we are not going deeper into tech debt? How do we make sure our solution is "as simple as possible, but not simpler" -- "simple is not easy"? Well, this is why &lt;strong&gt;pair programming&lt;/strong&gt; and &lt;strong&gt;code reviews&lt;/strong&gt; were invented in the first place. I am not worried about my job and about the jobs of the engineers I'm working with on my team. GenAI tools are here to stay and they are opening up opportunities we couldn’t dream of. &lt;/p&gt;

&lt;p&gt;P.S.  I recently also started to use GenAI for writing work emails and documentation. Writing boilerplate code with the help of GenAI is a must. For instance, writing Unit Tests manually these days is a sin. However, working on the articles like this one, I choose to write every sentence by hand. I do admit, my writing is not perfect, and most likely never will be, but -- it's authentic, and it has a direct reflection of my personality. And I do intend to keep it this way. I do intend to keep improving my writing with every post, and the only way to do it -- is to keep writing with my own brain :)&lt;/p&gt;

</description>
      <category>genai</category>
      <category>githubcopilot</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Works, don't touch it? Or never ending optimization game?</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Sat, 18 May 2024 15:09:24 +0000</pubDate>
      <link>https://forem.com/dmitryame/works-dont-touch-it-or-never-ending-optimization-game-1aoo</link>
      <guid>https://forem.com/dmitryame/works-dont-touch-it-or-never-ending-optimization-game-1aoo</guid>
      <description>&lt;p&gt;I've built a photo sharing app &lt;a href="https://www.wisaw.com"&gt;https://www.wisaw.com&lt;/a&gt;. &lt;br&gt;
I use it on my iPhone all the time, and it works great for me! &lt;/p&gt;

&lt;p&gt;Recently a friend complained that it's really slow on his iPad. So, I started digging...&lt;/p&gt;

&lt;p&gt;Turns out, the app retrieves the photos by calling GraphQL backend, 1 day at a time, until it fills the screen. On smaller devices works like a charm, but on tablets -- lots of network calls, and painful user experience. &lt;/p&gt;

&lt;p&gt;My goal was to figure out how to make a backend return 10 days (or more) worth of photos in one call, without making any changes to the frontend -- it's still a pain to re-deploy mobile apps. &lt;/p&gt;

&lt;p&gt;Here is my first bruit force approach:&lt;/p&gt;

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

&lt;p&gt;This actually worked. Right away, I saw the number of calls to AWS Lambda, where I host my graphQL backend, to drastically go down. Which means the application does not have to open and maintain as many network connections on slower bandwidth networks. The UX is much smoother. So, should I stop here? Well, there was still something that bothered me about the code. To my taste, the number of repetitive lines of code is insane -- typical Copy/Paste design pattern. Generally speaking I'm OK seeing it in the code when it has 2 occurrences, but 10!!! is just a way too many.&lt;/p&gt;

&lt;p&gt;Here is the revised version:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9dzy2ni9huonwe8kwuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9dzy2ni9huonwe8kwuc.png" alt="Image description" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much better. JS &lt;code&gt;Promise.all&lt;/code&gt; is awesome. The final version of the code is much shorter. The number of async calls can be adjusted dynamically by playing with &lt;code&gt;arraySize&lt;/code&gt; const. Each function call returns arrays of exactly the same objects types, so I can chain &lt;code&gt;flat(1)&lt;/code&gt; to the end. I can probably even consider passing the &lt;code&gt;arraySize&lt;/code&gt;, depending on the device screen size, into the lambda call at some point, but that's a different story for some other time (and that would require the mobile app new release). &lt;/p&gt;

&lt;p&gt;So, the complexity didn't really go away, arguably, the code became even more complex. But, I thought it was pretty cool to be able to optimize UX dynamics without touching a frontend. The spikiness of the network calls moved from mobile devices running on slow networks, to calls between the middleware and the database, which are much faster and reliable. &lt;/p&gt;

&lt;p&gt;Should I have touched the code to begin with? It was working OK after all. At the end of the day, the answer is -- yes! Absolutely! I've learned how to &lt;code&gt;Promise.all&lt;/code&gt; dynamically on a variable size array of functions. It took me like an hour of hacking on a Friday night before heading out to a weekend. &lt;br&gt;
And, I've had a lot of fun, and, my friend, who started all this, is happy running my app on his tablet -- that's all that matters.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>optimization</category>
    </item>
    <item>
      <title>What is the most minimalistic app you'd ever created?</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Tue, 07 Mar 2023 16:04:28 +0000</pubDate>
      <link>https://forem.com/dmitryame/what-is-the-most-minimalistic-app-youd-ever-created-232h</link>
      <guid>https://forem.com/dmitryame/what-is-the-most-minimalistic-app-youd-ever-created-232h</guid>
      <description>&lt;p&gt;I've been a big proponent of minimalism. &lt;br&gt;
Especially when it comes down to engineering.&lt;br&gt;
Especially for the software engineering.&lt;/p&gt;

&lt;p&gt;Albert Einstein -- Everything should be made as simple as possible, but not simpler. &lt;/p&gt;

&lt;p&gt;According to the second law of thermodynamics, in the physical world the entropy keeps increasing.&lt;br&gt;
So does the complexity of our software, and we, the software engineers, are to be blamed.&lt;/p&gt;

&lt;p&gt;I wrote about minimalistic architecture: &lt;a href="https://dev.to/dmitryame/minimalistic-architecture-for-minimalistic-product-ffd"&gt;https://dev.to/dmitryame/minimalistic-architecture-for-minimalistic-product-ffd&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I even tried to invent most minimalistic social sharing platform: &lt;a href="https://www.echowaves.com/"&gt;https://www.echowaves.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most minimalistic app I've ever came up with &lt;a href="https://www.echowaves.com/chimingclock"&gt;https://www.echowaves.com/chimingclock&lt;/a&gt; -- it's only about 100 lines of code, 1 screen, no customizations, but I still think of it as very useful app. Of course, there is a lot of complexity that went behind the scenes building it. It took me almost a week to find the right combination of those 100 lines to make it work -- simple is not easy.&lt;/p&gt;

&lt;p&gt;If you are as passionate about eliminating complexity in our lives (read in software we build every day), and if you've ever created something that just bare bones simple, share in comments about your experiences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XgoAihs3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6s0gcmor66c73abausld.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XgoAihs3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6s0gcmor66c73abausld.jpeg" alt="This machine will set us free" width="880" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Turning old Tablet into Chiming Clock, or why do we need more clock apps?</title>
      <dc:creator>Dmitry Amelchenko</dc:creator>
      <pubDate>Tue, 07 Mar 2023 16:04:12 +0000</pubDate>
      <link>https://forem.com/dmitryame/turning-old-tablet-into-chiming-clock-or-why-do-we-need-more-clock-apps-4p08</link>
      <guid>https://forem.com/dmitryame/turning-old-tablet-into-chiming-clock-or-why-do-we-need-more-clock-apps-4p08</guid>
      <description>&lt;h1&gt;
  
  
  Little bit of history
&lt;/h1&gt;

&lt;p&gt;I'm a computer guy. I like to know my time exactly down to the second. &lt;/p&gt;

&lt;p&gt;And, I'm also an engineer, and I love elegant mechanical things that simply work without any electricity.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;I've had this clock for over 20 years. Every week I had to rewind it with a key, which I didn't mind doing all these years. I rewinded the clock about 1000 times (52 weeks * 20 years = 1040). &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%2Fstatic.wixstatic.com%2Fmedia%2Fc90e7e_5510349cb7c6408791eaff4b1d479b0b~mv2.png%2Fv1%2Ffill%2Fw_1200%2Ch_900%2Cal_c%2Cq_90%2Cusm_0.66_1.00_0.01%2Cenc_auto%2Fc90e7e_5510349cb7c6408791eaff4b1d479b0b~mv2.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%2Fstatic.wixstatic.com%2Fmedia%2Fc90e7e_5510349cb7c6408791eaff4b1d479b0b~mv2.png%2Fv1%2Ffill%2Fw_1200%2Ch_900%2Cal_c%2Cq_90%2Cusm_0.66_1.00_0.01%2Cenc_auto%2Fc90e7e_5510349cb7c6408791eaff4b1d479b0b~mv2.png" title="The Old Clock" alt="The Old Clock" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
​&lt;br&gt;
The chimes every 15 minutes were loud and clear, but everyone in the house hardly noticed it.&lt;/p&gt;

&lt;p&gt;Eventually the spring started to wear off. So, every time after rewinding, I had to set the time couple of minutes ahead. By the middle of the week the clock would show correct time. And right before the next cycle, they would be 5 minutes late.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Currently, due to wear and tear the clock looses more than 15 minutes per week, I finally had to make a hard decision and stop rewinding it. &lt;/p&gt;

&lt;p&gt;This is sad -- all over sudden, something was missing. Hearing the chimes every 15 minutes (although the time was never precise) made the house alive.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;I had to do something about it -- I'm an engineer, I like solving challenges like this one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Problem definition
&lt;/h1&gt;

&lt;p&gt;So, why can't we have the best of both worlds -- the precise time and good old Westminster chimes? It turns out we can!&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;The mechanical clock may break one day. But there always will be some old tablet laying around which no one wants to use, because it's couple of versions old. &lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;I happened to have exactly that -- the old android tablet which was collecting dust in a drawer. &lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;The first thing I tried, I looked for applications on the app store. Who knows, perhaps someone has already built something that will work for my taste. Quickly I was disappointed. The solutions I found were a lot more complex than necessary.  They all had lots of customizations. They all were mostly rather big and complex. And non of them offered the chimes I wanted.&lt;/p&gt;

&lt;p&gt;I'm a true believer in simplicity when it comes down to engineering solutions. Simple things tend to last longer. &lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;So, I have to build something myself, just because the existing solutions were a way too complicated, still didn't offer exactly what I wanted, and just because I can.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;I want my new digital clock to behave as close as possible to my mechanical clock, with one exception -- they must show precise time.&lt;/p&gt;

&lt;p&gt;It should work out of the box, no customizations. Think of it this way -- you bring the clock home, you open the box, you wind it up for the first time, you set the correct time, and that's it -- it just works. You never had to customize the mechanical clock, yet, some how no one complained back then. It's really hard for me to understand this obsession with customization. &lt;/p&gt;

&lt;h3&gt;
  
  
  If you don't like my default options you can do one of 3 things:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Don't use it, find an alternative. The stores back then offered variety of colors, sizes, shapes, quality, price range and  brands. The app stores today most likely will have something that matches your desires as well. &lt;/li&gt;
&lt;li&gt;Reach out to me with suggestions. If I like your idea -- I may add it to the next version of the application --&amp;gt; &lt;a href="mailto:dmitry@echowaves.com"&gt;dmitry@echowaves.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Make customizations yourself. The code is opensource --&amp;gt;   &lt;a href="https://github.com/echowaves/chimingClock" rel="noopener noreferrer"&gt;https://github.com/echowaves/chimingClock&lt;/a&gt; . You can submit a pull request and, if it does not make the original software more complicated than it should be, I will merge it in. Or you can clone the repo and go wild.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Here are the customizations and features the application will not support:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Only supports landscape (I want my digits to occupy all available space on the screen).&lt;/li&gt;
&lt;li&gt;Only one color scheme.&lt;/li&gt;
&lt;li&gt;Only one type of font.&lt;/li&gt;
&lt;li&gt;No support for military time. Not even showing "am/pm". &lt;/li&gt;
&lt;li&gt;No support for seconds. &lt;/li&gt;
&lt;li&gt;No support for calendar features. &lt;/li&gt;
&lt;li&gt;No alarm, no timer.&lt;/li&gt;
&lt;li&gt;No support for dimming (can be done at the device level).&lt;/li&gt;
&lt;li&gt;No support for adjusting volume sound (can be done at the device level).&lt;/li&gt;
&lt;li&gt;No support for different types of chimes -- only Westminster. &lt;/li&gt;
&lt;li&gt;No support for weather updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mechanical clock didn't have any of these customizations and they worked great. If you really want to change any of these defaults -- the code is open source.&lt;/p&gt;

&lt;h1&gt;
  
  
  The solution
&lt;/h1&gt;

&lt;p&gt;Here is what I ended up building:&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%2Fstatic.wixstatic.com%2Fmedia%2Fc90e7e_e7519a3268f743cda1eeed6bcc4fb8e0~mv2.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%2Fstatic.wixstatic.com%2Fmedia%2Fc90e7e_e7519a3268f743cda1eeed6bcc4fb8e0~mv2.webp" title="final solution" alt="final solution" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The clock is a decent size, visible from every corner of the living room, the proportions are nicely balanced. The wire sticking from the side is a little annoying, but hey, you can't expect everything to be perfect.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;To make it stand, I had to glue a wooden cube to the back of the tablet with a piece of two sided sticky tape. &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%2Fstatic.wixstatic.com%2Fmedia%2Fc90e7e_15324c2ebe664606a0d9ef2cd9617d83~mv2.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%2Fstatic.wixstatic.com%2Fmedia%2Fc90e7e_15324c2ebe664606a0d9ef2cd9617d83~mv2.webp" title="make it stand" alt="make it stand" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's all. Nothing beats the feeling of satisfaction upon project completion.&lt;/p&gt;

&lt;p&gt;Investing few hours of time over the weekend saved me from buying something off of Amazon, and filled my house again with wonderful Westminster chimes I was beginning to miss when my old mechanical clock was retired.&lt;/p&gt;




&lt;p&gt;It was originally posted here &lt;a href="https://www.echowaves.com/chimingclock" rel="noopener noreferrer"&gt;https://www.echowaves.com/chimingclock&lt;/a&gt;&lt;br&gt;
The mobile app can be downloaded here: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://play.google.com/store/apps/details?id=com.echowaves.chimingclock" rel="noopener noreferrer"&gt;Android&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/chimingclock/id6446005837" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the &lt;a href="https://github.com/echowaves/chimingClock" rel="noopener noreferrer"&gt;source code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>personalization</category>
      <category>reactnative</category>
      <category>weekend</category>
    </item>
  </channel>
</rss>
