<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Andrea Baccega&apos;s Blog</title><description>Thoughts on software development, backend systems, and occasional cooking experiments.</description><link>https://andreabaccega.com/</link><language>en</language><item><title>Opus vs r/AskElectronics: 1-0 for the humans</title><link>https://andreabaccega.com/blog/opus-vs-askelectronics/</link><guid isPermaLink="true">https://andreabaccega.com/blog/opus-vs-askelectronics/</guid><description>I spent a day taking orders from a frontier LLM to debug a thermal problem on my eNSPanel PCB. Then a stranger on Reddit fixed it with one question.</description><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few years ago I started building &lt;a href=&quot;/blog/enspanel-custom-nspanel-pcb&quot;&gt;eNSPanel&lt;/a&gt;, an open-source custom PCB replacement for the Sonoff NSPanel. Each revision got a little better. Around the sixth or seventh revision I wanted to clean up the thermal management and finally call the design done.&lt;/p&gt;
&lt;p&gt;Then my son was born and &lt;a href=&quot;/blog/ai-solved-my-toddler-sleep-issues&quot;&gt;I stopped sleeping&lt;/a&gt;. The board sat in a drawer for months.&lt;/p&gt;
&lt;h2&gt;The chip that wouldn&apos;t behave&lt;/h2&gt;
&lt;p&gt;When I came back to it, the same regulator was &lt;em&gt;still&lt;/em&gt; cooking. The ST1S10PHR step-down was drawing absurd current and heating like a stove the instant I powered the board. Output rail flat. No oscillation, no soft recovery, just heat.&lt;/p&gt;
&lt;h2&gt;A day taking orders from Opus&lt;/h2&gt;
&lt;p&gt;So I did what people do in 2026. I gave the schematic, the layout, every measurement, and the datasheet to Claude Opus and started debugging with it. &lt;em&gt;Claude as the debugger, me as the manual labor.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We went all day.&lt;/p&gt;
&lt;p&gt;Cut this trace. Replace those caps. Swap the chip, it might be a counterfeit. Bridge here. Lift this pin. Measure pin 3 to ground again. Now pin 7. Now pin 1. Try a different input voltage. Add a pulldown. Remove the pulldown. Replace the chip again.&lt;/p&gt;
&lt;p&gt;I did all of it. I have the flux burns to prove it. By the evening I had &lt;strong&gt;eight dead ST1S10PHR samples&lt;/strong&gt;, a board that looked like a battlefield, and the &lt;em&gt;exact same symptom&lt;/em&gt; I had at 9am.&lt;/p&gt;
&lt;h2&gt;Ten minutes on Reddit&lt;/h2&gt;
&lt;p&gt;Out of options, I dumped everything into a &lt;a href=&quot;https://www.reddit.com/r/AskElectronics/comments/1rsq5qx/st1s10phr_misterious_behavior/&quot;&gt;r/AskElectronics post&lt;/a&gt;: schematic, layout, every measurement. I added the now-traditional disclaimer: &lt;em&gt;&quot;I even asked AI, but it didn&apos;t tell me anything valuable.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Reddit moderation held the post for a while. &lt;strong&gt;Ten minutes&lt;/strong&gt; after it went live, I had the answer.&lt;/p&gt;
&lt;p&gt;A user named &lt;strong&gt;Ard-War&lt;/strong&gt;, flair &quot;Electron Herder&quot;, looked at the schematic and asked exactly one question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Why is there 4μ7 across VIN and SW? I&apos;m surprised it wasn&apos;t outright going in flames with that.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A 4.7µF capacitor I had wired straight between VIN and the switching node. Every cycle, the chip was effectively shorting its own switching pin to the input rail. His follow-up read like a sentence you only write after years next to these parts:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;microfarads with microohm ESR is really in smokes and fires category.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One question. No new component. No exotic measurement. A schematic review by someone who has laid out hundreds of buck converters and &lt;em&gt;would never have placed a cap there in the first place&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I removed the offending cap, fixed the layout, populated a fresh chip, and the rail came up clean. I posted &lt;em&gt;&quot;Thanks IT works&quot;&lt;/em&gt; and the redesigned schematic. Ard-War reviewed that one too. Production ready.&lt;/p&gt;
&lt;h2&gt;Expertise is the guardrail&lt;/h2&gt;
&lt;p&gt;The takeaway is not &lt;em&gt;&quot;LLMs are bad&quot;&lt;/em&gt;. I use them every day, and on text problems they are extraordinary. The point is narrower, and, for me, important.&lt;/p&gt;
&lt;p&gt;For hardware, Opus was happy to give me a confident, plausible, &lt;em&gt;completely useless&lt;/em&gt; plan for an entire day. Not because it was lying, but because the failure mode was a switching-regulator layout error: a domain where the priors are very specific, the failure modes are visual, and the right answer usually comes from someone who has stared at a hundred buck-converter layouts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you are an expert in what you are asking the model to do, an LLM is an amplifier. You steer, it accelerates, and your existing knowledge keeps it from walking you off a cliff.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you are not, and the domain is unforgiving, you can spend a full day executing a sequence of expensive, irreversible moves that lead nowhere, and never even realize you are off the map.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I am no EE. I am a software engineer who solders. &lt;em&gt;That gap does not show up on any benchmark I have seen.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I lost a day and eight chips. Reddit cost me one post and a &lt;em&gt;&quot;Thanks IT works&quot;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For the kind of problems I usually solve, the model is winning by a mile. For this one, the model wasn&apos;t even close.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1-0.&lt;/strong&gt;&lt;/p&gt;
</content:encoded><category>ai</category><category>llm</category><category>hardware</category><category>electronics</category><category>war-story</category></item><item><title>Text is the New Binary</title><link>https://andreabaccega.com/blog/text-is-the-new-binary/</link><guid isPermaLink="true">https://andreabaccega.com/blog/text-is-the-new-binary/</guid><description>Software increasingly ships as prose meant to be interpreted by someone else&apos;s LLM. From Karpathy&apos;s wiki gist to gh skill install, even installation is now a paragraph. The same critique that pushes Karpathy beyond RAG hints at where the dust will eventually settle.</description><pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In April 2026, Andrej Karpathy published a &lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot;&gt;GitHub gist&lt;/a&gt;. Not code. Not a library. A markdown document describing a pattern: an LLM-maintained folder of wiki pages that compounds across sessions and beats RAG. Right at the top, he tells you exactly what kind of artifact it is:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&quot;This is an idea file, it is designed to be copy pasted to your own LLM Agent (e.g. OpenAI Codex, Claude Code, OpenCode / Pi, or etc.). Its goal is to communicate the high level idea, but your agent will build out the specifics in collaboration with you.&quot;&lt;/p&gt;
  &lt;footer&gt;Andrej Karpathy, &lt;cite&gt;&lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot; target=&quot;_blank&quot;&gt;LLM Wiki&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within weeks: tutorials, v2 forks, &quot;beyond RAG&quot; think-pieces, dozens of open-source implementations. People are &lt;em&gt;running&lt;/em&gt; this software. The artifact is prose. There&apos;s no &lt;code&gt;npm install&lt;/code&gt;, no version, no test suite. You read the gist, you instruct your agent, your agent builds you a wiki.&lt;/p&gt;
&lt;p&gt;I&apos;m running it too. &lt;strong&gt;That&apos;s a binary.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;The Reframe&lt;/h2&gt;
&lt;p&gt;Code is what you write. A binary is what you &lt;em&gt;ship&lt;/em&gt;: the thing users actually execute. Software used to ship as compiled artifacts with contracts: function signatures, exit codes, semver, reproducible builds. A growing class now ships as &lt;strong&gt;text meant to be interpreted by someone else&apos;s LLM at runtime&lt;/strong&gt;. No types. No version pin (which model? which context window? which mood?). No tests in the old sense. Just words and trust.&lt;/p&gt;
&lt;p&gt;Text is shipped. Text is executed. &lt;strong&gt;Text is the binary.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Even the Installation is Text&lt;/h2&gt;
&lt;p&gt;For decades, the &quot;installation&quot; section of a README was one of two things: a shell command, or a config file. &lt;code&gt;npm install foo&lt;/code&gt;. &lt;code&gt;curl … | bash&lt;/code&gt;. &lt;code&gt;docker pull&lt;/code&gt;. A deterministic instruction the human (or CI) executes verbatim.&lt;/p&gt;
&lt;p&gt;Now look at the install for &lt;a href=&quot;https://github.com/heilcheng/awesome-agent-skills&quot;&gt;awesome-agent-skills&lt;/a&gt;, an April 2026 directory of skills for Claude, Codex, and Copilot:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&quot;paste the raw SKILL.md URL in a new conversation. […] That&apos;s it. No installation. No configuration. No coding required.&quot;&lt;/p&gt;
  &lt;footer&gt;&lt;cite&gt;&lt;a href=&quot;https://github.com/heilcheng/awesome-agent-skills&quot; target=&quot;_blank&quot;&gt;awesome-agent-skills&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that again. The install command is a &lt;em&gt;sentence in English&lt;/em&gt;: &quot;paste the raw SKILL.md URL in a new conversation.&quot; There&apos;s no script. There&apos;s no package. There&apos;s a markdown file at a URL, and a paragraph telling you to hand the URL to your agent, which then reads the markdown and figures out the rest. The install instruction is prose; what it points to is prose; what the prose configures is the agent&apos;s behavior at runtime. &lt;strong&gt;It&apos;s prose all the way down.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Karpathy&apos;s gist is the same shape: &quot;copy paste this to your own LLM Agent&quot; &lt;em&gt;is&lt;/em&gt; the install command. So is every CLAUDE.md and every AGENTS.md, now a Linux Foundation standard adopted across 60,000+ projects[^1]: project configuration shipped as prose, no installer required. GitHub even shipped &lt;code&gt;gh skill install&lt;/code&gt;[^2] in April 2026, a first-class CLI command for installing markdown files.&lt;/p&gt;
&lt;p&gt;This isn&apos;t a fad. It&apos;s a real engineering shift, with reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Forkability.&lt;/strong&gt; Edit a paragraph, you have a v2.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model evolution.&lt;/strong&gt; The same prose gets sharper as the agent does.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personalization at runtime.&lt;/strong&gt; Don&apos;t like something about Karpathy&apos;s gist? Add a line to your prompt. Specific need your team has? Just tell the agent. Every reader of the same prose gets their own build, shaped by what they say next. No binary can do that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Format is Markdown&lt;/h2&gt;
&lt;p&gt;Notice how everything in the previous section is markdown. Karpathy&apos;s gist, AGENTS.md, SKILL.md, the blog post you&apos;re reading. That&apos;s not an accident; it&apos;s becoming the substrate.&lt;/p&gt;
&lt;p&gt;In February 2026, Cloudflare shipped &lt;a href=&quot;https://blog.cloudflare.com/markdown-for-agents/&quot;&gt;Markdown for Agents&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&quot;Markdown has quickly become the &lt;em&gt;lingua franca&lt;/em&gt; for agents and AI systems as a whole.&quot;&lt;/p&gt;
  &lt;footer&gt;Cloudflare, &lt;cite&gt;&lt;a href=&quot;https://blog.cloudflare.com/markdown-for-agents/&quot; target=&quot;_blank&quot;&gt;Introducing Markdown for Agents&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;Any site behind Cloudflare can now serve a markdown version of itself when the request includes &lt;code&gt;Accept: text/markdown&lt;/code&gt;. Claude Code and OpenCode already send that header. Cloudflare&apos;s own measurement: a blog post costs 16,180 tokens as HTML, 3,150 as markdown. &lt;strong&gt;An 80% reduction.&lt;/strong&gt; The web is starting to translate itself.&lt;/p&gt;
&lt;p&gt;If text is the new binary, markdown is its instruction set.&lt;/p&gt;
&lt;h2&gt;When the App Is the File&lt;/h2&gt;
&lt;p&gt;The escalation continues. If installation is prose and the format is markdown, entire products start collapsing into markdown files.&lt;/p&gt;
&lt;p&gt;In 2015, &quot;track what I eat today&quot; required someone to build you an app: a developer, a designer, a backend, an App Store listing. Months of work, thousands of dollars. In 2026, it&apos;s a sentence to an agent. No download, no onboarding, no app.&lt;/p&gt;
&lt;p&gt;Karpathy frames this arc as Software 3.0[^3]: code (1.0) → learned weights (2.0) → prompts (3.0). One-shot work doesn&apos;t even need an artifact: you say what you want and the agent does it. Recurring work gets a markdown file so you don&apos;t re-explain. Only the rest still wants code. People are already shipping &quot;skill graphs&quot;: folders of interlinked markdown that teach an LLM how to think about a domain, sold not as a SaaS but as a folder you own. &quot;Apps gave phones superpowers; skills give AI agents expertise.&quot;[^4]&lt;/p&gt;
&lt;p&gt;This won&apos;t replace every app. It will replace the long tail whose only real value was &lt;em&gt;encoding a workflow&lt;/em&gt;. Personalization stretches the tail further: when generating a custom version for one user is cheap, the market for one-size-fits-all SaaS shrinks[^5]. That tail is enormous.&lt;/p&gt;
&lt;h2&gt;The Paradox Karpathy Spotted&lt;/h2&gt;
&lt;p&gt;Here&apos;s the irony hiding inside Karpathy&apos;s own gist. The whole thesis of the LLM Wiki is a critique of how today&apos;s RAG systems handle knowledge:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&quot;the LLM is rediscovering knowledge from scratch on every question. There&apos;s no accumulation. […] The knowledge is compiled once and then &lt;em&gt;kept current&lt;/em&gt;, not re-derived on every query.&quot;&lt;/p&gt;
  &lt;footer&gt;Andrej Karpathy, &lt;cite&gt;&lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot; target=&quot;_blank&quot;&gt;LLM Wiki&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;But &quot;text as binary&quot; is structurally identical to RAG. The agent re-interprets the same prose every time: every CLAUDE.md loaded into a new session, every SKILL.md matched against a prompt and read into context. The same critique that pushes Karpathy beyond RAG will eventually push us beyond text-as-binary for the workloads where re-derivation is too expensive: high frequency, low margin, regulated, audited.&lt;/p&gt;
&lt;p&gt;That&apos;s the real shape of the next few years. We&apos;re in an exuberant phase. Text-as-binary is shipping into places it&apos;ll eventually retreat from. Try debugging an agent that decided to skip a step in your workflow, or auditing a refund issued because a CLAUDE.md said &quot;be helpful.&quot; Meanwhile the deterministic binary is still sitting in places it&apos;ll eventually cede: every workflow that&apos;s 80% similar to one you support but not exactly.&lt;/p&gt;
&lt;p&gt;The fight is loud because the equilibrium hasn&apos;t arrived yet. Both approaches are oversold and underspecified at the boundary. That&apos;s a temporary condition.&lt;/p&gt;
&lt;h2&gt;Where the Line Lands&lt;/h2&gt;
&lt;p&gt;A heuristic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Text wins&lt;/strong&gt; when the surface area is infinite and correctness is fuzzy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code wins&lt;/strong&gt; when the contract is auditable, the bill is real, or &lt;strong&gt;re-derivation is too expensive&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most production systems will need both: text at the edges (interpretation, dispatch), code in the core (execution, money, security). The interesting engineering of the next five years isn&apos;t picking sides. It&apos;s finding where the line falls for your specific problem.&lt;/p&gt;
&lt;p&gt;This blog is a worked example. You&apos;re reading prose: a markdown file in a &lt;code&gt;src/content/blog/&lt;/code&gt; folder, compiled to HTML by Astro at build time. Text at the edge (the writing), deterministic code at the core (the build pipeline). No LLM mediating the runtime. The line falls there. For my CLAUDE.md, it falls somewhere else. For Karpathy&apos;s gist, somewhere else again.&lt;/p&gt;
&lt;p&gt;Karpathy himself flags this in the closing of the gist:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&quot;The document&apos;s only job is to communicate the pattern. Your LLM can figure out the rest.&quot;&lt;/p&gt;
  &lt;footer&gt;Andrej Karpathy, &lt;cite&gt;&lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot; target=&quot;_blank&quot;&gt;LLM Wiki&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is doing two things at once. It&apos;s a confession that the artifact has limits. And it&apos;s a bet that, for &lt;em&gt;this&lt;/em&gt; workload, the limits are worth the leverage. Both can be true. That&apos;s where the line will eventually land, not on text or code, but on which one carries the leverage worth its limits, for the problem in front of you.&lt;/p&gt;
&lt;p&gt;[^1]: AGENTS.md was originally popularized by OpenAI&apos;s Codex CLI and was &lt;a href=&quot;https://www.linuxfoundation.org/press/linux-foundation-announces-the-formation-of-the-agentic-ai-foundation&quot;&gt;donated to the Linux Foundation&apos;s Agentic AI Foundation in December 2025&lt;/a&gt; as one of three founding projects (alongside Anthropic&apos;s Model Context Protocol and Block&apos;s goose). Adopted across 60,000+ open-source projects and agent frameworks.&lt;/p&gt;
&lt;p&gt;[^2]: GitHub&apos;s &lt;a href=&quot;https://github.blog/changelog/2026-04-16-manage-agent-skills-with-github-cli/&quot;&gt;&lt;code&gt;gh skill install&lt;/code&gt; changelog&lt;/a&gt;, April 16, 2026.&lt;/p&gt;
&lt;p&gt;[^3]: Andrej Karpathy, &quot;Software 3.0,&quot; YC AI Startup School keynote, 2025. &lt;a href=&quot;https://www.latent.space/p/s3&quot;&gt;Latent Space transcript and discussion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[^4]: From &lt;a href=&quot;https://openclaw.rocks/blog/ai-skills-are-the-new-apps&quot;&gt;&quot;AI Skills Are the New Apps&quot;&lt;/a&gt;, OpenClaw, 2026.&lt;/p&gt;
&lt;p&gt;[^5]: Software ETFs fell roughly 30% in early 2026, with Salesforce, Adobe, and ServiceNow each off 25-30%. Analysts are calling it &quot;AI eating software.&quot; See Fortune&apos;s coverage of &lt;a href=&quot;https://fortune.com/2026/04/06/the-real-impact-of-ai-on-saas-isnt-what-investors-think/&quot;&gt;the SaaSpocalypse&lt;/a&gt; and a16z&apos;s &lt;a href=&quot;https://a16z.com/notes-on-ai-apps-in-2026/&quot;&gt;&quot;Notes on AI Apps in 2026&quot;&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>ai</category><category>llm</category><category>claude-code</category><category>agents</category><category>opinion</category></item><item><title>Why I Switched to a Split Keyboard (and Why You Should Too)</title><link>https://andreabaccega.com/blog/why-i-switched-to-a-split-keyboard/</link><guid isPermaLink="true">https://andreabaccega.com/blog/why-i-switched-to-a-split-keyboard/</guid><description>From QWERTY&apos;s historical accident to the Temper split keyboard — my journey into ergonomic typing, the surprising history of keyboard layouts, and a practical guide to making the switch.</description><pubDate>Thu, 29 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been typing on traditional keyboards for over 25 years. Thousands of hours, millions of keystrokes, all on a layout that was designed to &lt;strong&gt;slow you down&lt;/strong&gt;. Back in 2024, I took the leap and got to build a &lt;a href=&quot;https://github.com/raeedcho/temper&quot;&gt;Temper split keyboard&lt;/a&gt;. Here&apos;s why... and why you might want to follow.&lt;/p&gt;
&lt;h2&gt;Meet QWERTY: The Accidental Standard&lt;/h2&gt;
&lt;p&gt;Let&apos;s start with a question most of us never ask: &lt;strong&gt;why are the keys on your keyboard arranged the way they are?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The QWERTY layout was patented in 1878 by Christopher Latham Sholes[^2], the inventor of the first commercially successful typewriter. The popular story is that the layout was designed to prevent mechanical typebars from jamming by placing commonly used letter pairs far apart. Whether that&apos;s the full truth or a simplification, one thing is certain: &lt;strong&gt;QWERTY was designed for a mechanical constraint that hasn&apos;t existed for over a century.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When computers replaced typewriters, nobody questioned the keyboard layout. It was already the standard. Billions of people learned to type on QWERTY, and the network effect[^1] made it nearly impossible to displace. We&apos;ve been collectively carrying the baggage of 1870s engineering ever since.&lt;/p&gt;
&lt;p&gt;Alternative layouts like Dvorak (1936) and Colemak (2006) have tried to optimize for finger movement and comfort, but adoption remains tiny. The real problem, though, isn&apos;t just the &lt;strong&gt;layout&lt;/strong&gt; ... it&apos;s the &lt;strong&gt;shape&lt;/strong&gt; of the keyboard itself (and more)...&lt;/p&gt;
&lt;h2&gt;The Problem With Traditional Keyboards&lt;/h2&gt;
&lt;p&gt;Take a look at your hands right now. Let them rest naturally in front of you.&lt;/p&gt;
&lt;p&gt;Notice something? &lt;strong&gt;Your hands aren&apos;t parallel.&lt;/strong&gt; They&apos;re angled slightly outward. Your fingers don&apos;t all reach the same distance. Your thumbs are the strongest digits you have, yet on a traditional keyboard they share a single oversized spacebar.&lt;/p&gt;
&lt;p&gt;A standard keyboard forces you into an unnatural position:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ulnar deviation&lt;/strong&gt;: Your wrists bend outward to align your fingers with the parallel key rows. Do this for 8 hours a day, and you&apos;re on the express lane to RSI[^3].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pronation&lt;/strong&gt;: Your forearms rotate inward so your palms face down, compressing the nerves and tendons in your wrists.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thumb waste&lt;/strong&gt;: Your two most dexterous fingers are relegated to hitting one key each (spacebar and maybe Alt).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pinky overload&lt;/strong&gt;: Modifiers like Shift, Ctrl, and Enter are all assigned to your weakest finger.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I didn&apos;t think much about any of this until life forced my hand... literally. When my baby was born, he wouldn&apos;t sleep unless he was resting on my or my wife&apos;s chest. Any parent who&apos;s been through this knows: you&apos;re pinned to the couch or chair for hours, one arm cradling a sleeping newborn, desperately trying not to move. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/keyaboard-baby-problem.jpg&quot; alt=&quot;Trying to type on a regular keyboard with a baby sleeping on my chest — not ideal&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I had to keep working on projects and a traditional keyboard was out of the question (see above). But with a split keyboard, I could place each half on either side of me, type with my hands in whatever position the baby allowed, and still get some work done during those long naps. That&apos;s what pushed me to finally make the switch!&lt;/p&gt;
&lt;h2&gt;Why Now?&lt;/h2&gt;
&lt;p&gt;There&apos;s never been a better time to invest in your typing setup. With the rise of AI-powered tools like Claude, ChatGPT, and Copilot, &lt;strong&gt;prompts are the new code&lt;/strong&gt;. The quality of what you type directly shapes the quality of what you get back. Typing well, fast, accurately, and comfortably, matters more than ever.&lt;/p&gt;
&lt;p&gt;&quot;But what about voice input?&quot; you might ask. Tools like &lt;a href=&quot;https://wisprflow.ai/&quot;&gt;WisprFlow&lt;/a&gt; and other speech-to-text solutions are impressive, but they don&apos;t work everywhere. In an open office, on a train, during your kid&apos;s quiet hours, in a library, or when you&apos;re discussing anything sensitive or proprietary you can&apos;t just speak out loud. The keyboard remains your most reliable, private, and universal input device.&lt;/p&gt;
&lt;p&gt;And if you&apos;re going to rely on a keyboard for the foreseeable future, it makes sense to use one that&apos;s actually designed for human hands. At ~100 WPM, you&apos;re already faster than 97% of all typists — and a split keyboard can get you there (also non-split keyaboards :))&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/keybr-distribution.png&quot; alt=&quot;Typing speed distribution — 99.3 WPM beats 97.42% of all users&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Why Split?&lt;/h2&gt;
&lt;p&gt;A split keyboard addresses these issues by design:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/ergo-comparison-split.jpg&quot; alt=&quot;Ergonomic comparison of split keyboards&quot; /&gt;
&lt;em&gt;Image credit: &lt;a href=&quot;https://boardsource.xyz/blogs/guides/split-keyboard-benefits&quot;&gt;Boardsource&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Natural hand position&lt;/h3&gt;
&lt;p&gt;Each half of the keyboard can be placed and eventually angled independently, so your wrists stay straight. No more ulnar deviation. You can even tent the halves (angle them upward) to reduce pronation.&lt;/p&gt;
&lt;h3&gt;Thumb clusters&lt;/h3&gt;
&lt;p&gt;Instead of wasting your thumbs on a spacebar, split keyboards give them a cluster of keys with different actions. Your strongest fingers finally pulling their weight.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/thumb-cluster.png&quot; alt=&quot;Thumb cluster on the Temper split keyboard&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Columnar stagger&lt;/h3&gt;
&lt;p&gt;Traditional keyboards have rows staggered horizontally, a leftover from typewriter mechanics. Split keyboards typically use &lt;strong&gt;columnar stagger&lt;/strong&gt;, where keys are aligned vertically and offset to match the natural length of each finger. It feels weird for about a day, then it feels &lt;em&gt;right&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/stagger-comparison.jpg&quot; alt=&quot;Row stagger vs columnar layout comparison&quot; /&gt;
&lt;em&gt;Image credit: &lt;a href=&quot;https://keyboardsexpert.com/ortholinear-vs-staggered-keyboards/&quot;&gt;Keyboards Expert&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Fewer keys, more layers&lt;/h3&gt;
&lt;p&gt;The Temper has significantly fewer keys than a traditional keyboard. At first this sounds like a downside, but it means your fingers never leave the home row[^4]. Everything is accessible through something called layers... Think of it like holding Shift to get capital letters, but expanded to give you numbers, symbols, navigation, and function keys all within finger&apos;s reach.&lt;/p&gt;
&lt;h3&gt;Programmability&lt;/h3&gt;
&lt;p&gt;Every key does exactly what you want. With firmware like &lt;a href=&quot;https://zmk.dev/&quot;&gt;ZMK&lt;/a&gt;, you can define tap-hold behaviors (tap a key for one thing, hold it for another), combos, macros, and multiple layers. Your keyboard adapts to &lt;strong&gt;you&lt;/strong&gt;, not the other way around.&lt;/p&gt;
&lt;p&gt;For my Temper, I went with &lt;a href=&quot;https://github.com/manna-harbour/miryoku_zmk&quot;&gt;Miryoku&lt;/a&gt;... an ergonomic, minimal, orthogonal, and universal keyboard layout designed for 3x5 column keyboards with 3 thumb keys per hand (36 keys total). Miryoku gives you a well-thought-out set of layers out of the box: navigation, mouse emulation, numbers, symbols, function keys, and media controls. You don&apos;t have to design your own layout from scratch. It&apos;s a great starting point, and you can always tweak it to your liking later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/miryoku-kle-cover-miryoku_zmk.png&quot; alt=&quot;Miryoku ZMK default layout — all layers accessible from the thumb keys&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Obviously, being a developer i needed a tweak here and there so i customized for my own needs over time. In case you&apos;re curious you can find my firmware &lt;a href=&quot;https://github.com/vekexasia/miryoku_zmk/tree/veke&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;My Experience With the Temper &amp;amp; Miryoku&lt;/h2&gt;
&lt;p&gt;The Temper is a minimal, low-profile split keyboard. It&apos;s not for everyone... you need to solder. But that&apos;s part of the charm. You understand every component of the tool you use every day and gives you the knowledge on how to eventually fix it yourself.&lt;/p&gt;
&lt;p&gt;Here&apos;s what my first few weeks looked like:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 1-3&lt;/strong&gt;: Absolute chaos. My typing speed dropped from ~100 WPM to roughly 15 WPM. I kept reaching for keys that weren&apos;t there. Every muscle memory shortcut was broken. I questioned every life decision that led me to this point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 4-7&lt;/strong&gt;: Something started clicking. Literal keys, but also neural pathways. I stopped looking at the keyboard. The columnar layout started making sense. My thumbs were learning their new responsibilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Week 3&lt;/strong&gt;: I was back to ~80 WPM. &lt;/p&gt;
&lt;h2&gt;Taking the Leap of Faith&lt;/h2&gt;
&lt;p&gt;If you&apos;re considering making the switch, here&apos;s my practical advice:&lt;/p&gt;
&lt;h3&gt;1. Commit to it&lt;/h3&gt;
&lt;p&gt;The biggest mistake is keeping your old keyboard around &quot;just in case.&quot; You&apos;ll always fall back to it when deadlines hit, and you&apos;ll never build the muscle memory. &lt;/p&gt;
&lt;h3&gt;2. Start with a typing tutor&lt;/h3&gt;
&lt;p&gt;Use &lt;a href=&quot;https://www.keybr.com/&quot;&gt;keybr.com&lt;/a&gt; for 15-20 minutes each morning. Focus on accuracy, not speed. Speed comes naturally once the patterns are burned in. I came so obsessed with speed that keybr became a game for me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/keybr.png&quot; alt=&quot;My keybr.com stats — 99.3 WPM at 100% accuracy&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4. Go all in&lt;/h3&gt;
&lt;p&gt;If you also want to change layout (Eg go from QWERTY to DVORAK), do it at the same time. You&apos;ll be extremely frustrated at first but you don&apos;t have to re-learn after.&lt;/p&gt;
&lt;h3&gt;5. Be patient with yourself&lt;/h3&gt;
&lt;p&gt;Your productivity will tank temporarily. Plan for it. Don&apos;t start during a critical project sprint. But know that the dip is temporary, and what&apos;s on the other side is worth it.&lt;/p&gt;
&lt;h3&gt;6. Join the community&lt;/h3&gt;
&lt;p&gt;The mechanical keyboard and ergonomic keyboard communities are incredibly welcoming. Subreddits like r/ErgoMechKeyboards, Discord servers, and forums are full of people who&apos;ve gone through exactly what you&apos;re experiencing.&lt;/p&gt;
&lt;h2&gt;Unexpected Perks&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/blog/split-kb-tea.jpeg&quot; alt=&quot;My keybr.com stats — 99.3 WPM at 100% accuracy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Beyond the ergonomics and the typing experience, there are some surprisingly practical benefits to a split keyboard that nobody talks about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Coffee goes in the middle.&lt;/strong&gt; With two keyboard halves spread apart, there&apos;s a natural spot right in the center of your desk for your coffee or tea. This might sound trivial, but think about how many times you&apos;ve knocked over a mug while reaching across a traditional keyboard. With a split layout, your beverage sits safely between the halves, well away from your arm&apos;s sweeping path. I haven&apos;t spilled a drink on my desk since making the switch (well I shouldn&apos;t but I got some skills and can manage to mess up anyways).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Baby-compatible.&lt;/strong&gt; As I mentioned, a split keyboard lets you type in positions that would be impossible with a standard board. Each half can go wherever your hands happen to be — on armrests, on either side of a sleeping baby, on the couch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desk real estate.&lt;/strong&gt; A mouse, a notepad, a tablet. There&apos;s suddenly room for things in the middle of your workspace that a full-width keyboard used to occupy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portable ergonomics&lt;/strong&gt;. You can take your split keyboard halves to different locations and set them up ergonomically wherever you go. Being small allows to be carried easily without eating up too much space in your bag.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;And some cons&lt;/h2&gt;
&lt;p&gt;Well, nothing is perfect. Apart from the learning curve you just cannot go back after using a split keyboard. This means that when asked help from a fellow coworker or friend on their computer you will be at a disadvantage and will feel stress :).&lt;/p&gt;
&lt;p&gt;But bare with me... after a while you will get used to it and will be able to switch back and forth with some practice.&lt;/p&gt;
&lt;h2&gt;Is It Worth It?&lt;/h2&gt;
&lt;p&gt;I&apos;ll be honest: the switch is painful. There&apos;s no sugarcoating the first week. But if you spend significant hours typing every day, investing in how you interface with your computer is one of the highest-leverage improvements you can make.&lt;/p&gt;
&lt;p&gt;I am now faster than when I was using a standard keyboard. My typing feels more intentional. And there&apos;s something deeply satisfying about using a tool that was designed around human anatomy rather than 150-year-old mechanical constraints.&lt;/p&gt;
&lt;p&gt;The QWERTY keyboard served us well. But it&apos;s 2026, and we can do better.&lt;/p&gt;
&lt;p&gt;[^2]: U.S. Patent No. 207,559 — &quot;Improvement in Type-Writing Machines,&quot; granted to Christopher Latham Sholes on August 27, 1878. This patent contains the first documented appearance of the QWERTY keyboard layout. You can view it on &lt;a href=&quot;https://patents.google.com/patent/US207559A&quot;&gt;Google Patents&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[^3]: Repetitive Strain Injury (RSI) is a broad term for pain and damage caused by repetitive movements and sustained awkward postures. Research shows that keyboard use involving rapid, repetitive movements and static loading — such as holding wrists and shoulders in the same position for extended periods — is a significant contributing factor. A &lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC12089234/&quot;&gt;scoping review on upper limb RSI prevention&lt;/a&gt; found that ergonomic equipment, particularly adapted keyboards, was among the most studied intervention strategies. See also Pascarelli &amp;amp; Quilter&apos;s &lt;a href=&quot;https://pubmed.ncbi.nlm.nih.gov/9493794/&quot;&gt;pathomechanics study on keyboard RSI&lt;/a&gt; for a detailed look at the mechanisms involved.&lt;/p&gt;
&lt;p&gt;[^4]: The home row is the middle row of letter keys on a keyboard — the row where your fingers naturally rest. On a QWERTY keyboard, that&apos;s A-S-D-F for the left hand and J-K-L-; for the right hand. Touch typists are trained to always return their fingers to this row between keystrokes. On a well-designed split keyboard, almost every key you need is reachable without moving your fingers away from this position.&lt;/p&gt;
&lt;p&gt;[^1]: The network effect is a phenomenon where a product or technology becomes more valuable as more people use it. In the case of QWERTY, the more people who learned to type on it, the more typewriters and later computers were manufactured with that layout, which in turn meant more people learned QWERTY — creating a self-reinforcing cycle that made switching to a better layout economically and socially impractical.&lt;/p&gt;
</content:encoded><category>keyboards</category><category>ergonomics</category><category>split-keyboard</category><category>temper</category><category>productivity</category></item><item><title>AI (almost) solved my toddler sleep issues</title><link>https://andreabaccega.com/blog/ai-solved-my-toddler-sleep-issues/</link><guid isPermaLink="true">https://andreabaccega.com/blog/ai-solved-my-toddler-sleep-issues/</guid><description>How I used AI and open-source tools to monitor and improve my toddler&apos;s sleep patterns.</description><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It was 3 AM. Again. My toddler was screaming, I was running on fumes, and somewhere between the fourth lullaby and the fifth trip to his room, I thought: &lt;em&gt;&quot;There has to be a better way.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Spoiler: there was. After trying various traditional methods without much success, I decided to turn to AI for help.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I combined &lt;a href=&quot;https://frigate.video/&quot;&gt;Frigate&lt;/a&gt; (AI-powered video surveillance), a custom CNN model, and &lt;a href=&quot;https://github.com/babybuddy/babybuddy&quot;&gt;Babybuddy&lt;/a&gt; to automatically track my toddler&apos;s sleep patterns. The model detects not just if he&apos;s in bed, but whether he&apos;s lying down or sitting up—all in ~15ms with 99%+ accuracy.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Our toddler was having trouble sleeping through the night. He would wake up multiple times, cry, and have difficulty falling back asleep. This was taking a toll on our entire family, as we were all exhausted from the constant interruptions. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;20-30% of toddlers experience sleep disturbances.&lt;/strong&gt;[^1] In Western countries like the United States, Canada, and Australia, approximately 25% of parents report their babies or toddlers have sleep troubles[^2].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;A survey of 1,300+ parents found that parents lose approximately &lt;strong&gt;1,000+ hours of sleep&lt;/strong&gt; in the first year alone[^3]. That&apos;s over a month of sleepless nights!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;[^1]: Montgomery P, Dunne D. (2007). &quot;Sleep disorders in children&quot;. &lt;em&gt;BMJ Clinical Evidence&lt;/em&gt;. 2007:2304. &lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC2943792/&quot;&gt;PMC2943792&lt;/a&gt;
[^2]: Mindell JA, Sadeh A, Wiegand B, How TH, Goh DYT. (2010). &quot;Cross-cultural differences in infant and toddler sleep&quot;. &lt;em&gt;Sleep Medicine&lt;/em&gt;. 11(3):274-80. &lt;a href=&quot;https://pubmed.ncbi.nlm.nih.gov/20138578/&quot;&gt;DOI:10.1016/j.sleep.2009.04.012&lt;/a&gt;
[^3]: Snuz Sleep Survey (2022). Survey of 1,300+ UK parents found 70% lose an average of 3 hours&apos; sleep per night in their baby&apos;s first year (~1,095 hours annually). &lt;a href=&quot;https://www.snuz.co.uk/blogs/sleep-talk/the-truth-about-sleep-snuz-sleep-survey-results&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The Canonical solution&lt;/h2&gt;
&lt;p&gt;We read books, consulted pediatricians, and tried various sleep training methods. While some of these approaches provided temporary relief, the underlying issues persisted.&lt;/p&gt;
&lt;p&gt;As a last resort we decided to try one of what felt to be a big scam... Sleep consultants. We hired not 1 but 2 sleep consultants who provided us with a structured plan to help our toddler develop better sleep habits. The consultant advertised their methods as &quot;tailored to your child&apos;s needs&quot; and &quot;based on the latest research in child development.&quot; while in reality we spent about 1000$ to receive very well known generic advice that we could have found online (or already read in books). &lt;/p&gt;
&lt;p&gt;So we were back to square one, feeling frustrated and desperate for a solution.&lt;/p&gt;
&lt;h2&gt;If you can&apos;t measure it, you can&apos;t improve it&lt;/h2&gt;
&lt;p&gt;Before our child was born, I&apos;d already installed &lt;a href=&quot;https://github.com/babybuddy/babybuddy&quot;&gt;Babybuddy&lt;/a&gt;, an open source baby tracking web application, to track feedings, diaper changes, sleep, and other activities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/babybuddy-1.png&quot; alt=&quot;Babybuddy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The picture above shows the sleep patterns of our toddler over some weeks. We could see the frequent awakenings and the total sleep duration.&lt;/p&gt;
&lt;p&gt;Tools like this allowed us to collect data on our toddler&apos;s sleep patterns, which we could then analyze to identify potential issues.&lt;/p&gt;
&lt;p&gt;Unfortunately, when you are a sleep deprived parent, you don&apos;t have the time nor the energy to analyze data nor to update it regularly.&lt;/p&gt;
&lt;h2&gt;Enter AI&lt;/h2&gt;
&lt;p&gt;During a particularly rough night, I stumbled upon an article discussing the use of AI in healthcare and parenting. This sparked an idea: &lt;strong&gt;what if we could leverage AI to track automatically our toddler&apos;s sleep patterns and then eventually provide personalized recommendations?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After all, AI has been successfully used in various domains to analyze complex data and provide insights.&lt;/p&gt;
&lt;p&gt;Anyone familiar with the self-hosting community knows that &lt;a href=&quot;https://frigate.video/&quot;&gt;Frigate&lt;/a&gt; is the go-to open source solution for local AI-powered video surveillance. It uses TensorFlow Lite models to perform real-time object detection on video streams from IP cameras. &lt;/p&gt;
&lt;p&gt;I already had an ip-camera installed in the toddler&apos;s room for monitoring purposes already hooked into Frigate. So I decided to explore whether I could use Frigate&apos;s AI capabilities to monitor my toddler&apos;s sleep patterns.&lt;/p&gt;
&lt;h3&gt;What does Frigate offer?&lt;/h3&gt;
&lt;p&gt;Well, frigate is not meant to be used for sleep tracking, so I had to get creative. Frigate has &lt;a href=&quot;https://docs.frigate.video/api/&quot;&gt;APIs&lt;/a&gt; that allow you to access detected objects and events. I set up Frigate to monitor the video feed from the toddler&apos;s room and configured properly to record up to 24h of footage.&lt;/p&gt;
&lt;p&gt;This gave me access to raw training data (the camera frames) I could use for later. Frigate also provides insights about movement in the room, which I could use to infer sleep patterns such as deep &amp;amp; light sleep.&lt;/p&gt;
&lt;h3&gt;What I (and mommy) want&lt;/h3&gt;
&lt;p&gt;For our son to sleep through the night!!&lt;/p&gt;
&lt;p&gt;I would like to have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sleep patterns properly populated into babybuddy automatically&lt;/li&gt;
&lt;li&gt;More insights about sleep quality (eg: number of awakenings, total sleep time, sleep efficiency, etc.)&lt;/li&gt;
&lt;li&gt;Recommendations to improve sleep based on the data collected&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The plan&lt;/h3&gt;
&lt;p&gt;So the idea was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use current homelab systems such as Frigate, BabyBuddy, Home Assistant&lt;/li&gt;
&lt;li&gt;Build multiple AI models to analyze the video feed from the toddler&apos;s room&lt;/li&gt;
&lt;li&gt;Feed in data automatically into babybuddy&lt;/li&gt;
&lt;li&gt;Provide actionable daily insights based on the data collected&lt;/li&gt;
&lt;li&gt;Keep everything &lt;strong&gt;local-only&lt;/strong&gt; to preserve privacy&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Implementation&lt;/h3&gt;
&lt;p&gt;I had Claude write for me a script to download past 24h images at 5s interval to have some training data for the CNN model it was going to help me build. To refine data I always used claude to perform tedious tasks like download the footage, extract frames, label them, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/claude-code-ai-toddler-sleep.png&quot; alt=&quot;Claude code&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;YoLOv8 + Transfer Learning&lt;/h4&gt;
&lt;p&gt;We used &lt;a href=&quot;https://github.com/ultralytics/ultralytics&quot;&gt;YOLOv8&lt;/a&gt; as the base architecture for the CNN model. It&apos;s a state-of-the-art object detection model that is both fast and accurate, making it suitable for real-time applications with low inference time like this.&lt;/p&gt;
&lt;p&gt;We used a technique called &lt;em&gt;transfer learning&lt;/em&gt;[^transfer-learning], where we took a pre-trained YOLOv8 model and fine-tuned it on our specific dataset of toddler sleep images. This approach significantly reduces the amount of data and training time required to achieve good performance.&lt;/p&gt;
&lt;p&gt;Two main classifiers were needed:&lt;/p&gt;
&lt;p&gt;Presence classifier:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;empty&lt;/code&gt; - no toddler in bed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;baby_alone&lt;/code&gt; - toddler alone in bed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;baby_with_adult&lt;/code&gt; - toddler with adult in bed (likely mommy trying to soothe him back to sleep)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Posture classifier (only when &lt;code&gt;baby_alone&lt;/code&gt;):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;lying_down&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sitting_up&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I then proceed to label some of the frames keeping classes as balanced as possible.[^classimbalance]&lt;/p&gt;
&lt;p&gt;[^classimbalance]: Buda M, Maki A, Mazurowski MA. (2018). &quot;A systematic study of the class imbalance problem in convolutional neural networks&quot;. &lt;em&gt;Neural Networks&lt;/em&gt;. 106:249-259. &lt;a href=&quot;https://arxiv.org/abs/1710.05381&quot;&gt;DOI:10.1016/j.neunet.2018.07.011&lt;/a&gt;
[^transfer-learning]: Ribani R, Marengoni M. (2019). &quot;A Survey of Transfer Learning for Convolutional Neural Networks&quot;. &lt;em&gt;32nd SIBGRAPI Conference on Graphics, Patterns and Images Tutorials&lt;/em&gt;. pp. 47-57. &lt;a href=&quot;https://ieeexplore.ieee.org/document/8920338/&quot;&gt;DOI:10.1109/SIBGRAPI-T.2019.00010&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One challenge was the camera angle: it&apos;s mounted almost top-down above the bed. While this made presence detection trivial, distinguishing between &lt;code&gt;sitting_up&lt;/code&gt; and &lt;code&gt;lying_down&lt;/code&gt; turned out to be surprisingly tricky from above, a sitting toddler doesn&apos;t look that different from a lying one (to the AI &quot;eye&quot;). This required collecting significantly more training samples for the posture classifier than I initially expected.&lt;/p&gt;
&lt;p&gt;After only a couple of training iterations, the models were already achieving &amp;gt;99% accuracy on the validation set.&lt;/p&gt;
&lt;h3&gt;Connecting the dots&lt;/h3&gt;
&lt;p&gt;With the classifier working reliably, I had Claude build a simple web interface so I could monitor the predictions from my phone during critical moments—like bedtime. This let me validate the model&apos;s accuracy in real-time. &lt;/p&gt;
&lt;div&gt;
  &lt;img src=&quot;/blog/sleep-monitor.png&quot; alt=&quot;Web interface&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;The next step was to connect everything to BabyBuddy&apos;s APIs, automatically logging sleep data without any manual input. &lt;/p&gt;
&lt;p&gt;Provided the necessary API endpoints and authentication details, Claude generated the code to push sleep data into BabyBuddy based on the model&apos;s predictions.&lt;/p&gt;
&lt;h2&gt;Fighting feature creep&lt;/h2&gt;
&lt;p&gt;Once the basic system was working, my brain started doing what engineer brains do:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;I should use the audio profile from the camera to detect crying...&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;Wait, why don&apos;t I use the motion data from Frigate to detect restlessness?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;Oh wow, I could even add a sensor in Home Assistant to track room temperature and correlate it with sleep quality!&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I had to stop myself. The simple classifier was already solving the problem. Every additional feature meant more complexity, more things to break, and more time spent tinkering instead of sleeping.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sometimes good enough really is good enough.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;h3&gt;No more manual logging&lt;/h3&gt;
&lt;p&gt;The system now runs 24/7, quietly logging every sleep event to BabyBuddy with minute-level precision. I don&apos;t even check the dashboard anymore each morning. When we need to, we just ask Claude to summarize the night using the BabyBuddy APIs, and we get a clear picture of how things went.&lt;/p&gt;
&lt;p&gt;Before this, my wife and I would often disagree on how many times our toddler woke up—&lt;em&gt;was it three times or four? Did that 2am crying spell last five minutes or twenty?&lt;/em&gt; These fuzzy memories led to unreliable data, which made it impossible to spot patterns or make informed decisions about naps and bedtime.&lt;/p&gt;
&lt;h3&gt;Hidden insights&lt;/h3&gt;
&lt;p&gt;The system also answered questions we couldn&apos;t even ask before:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Was he awake but silent, hoping to go unnoticed?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;How long did he sit there before the crying started?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are things parents simply can&apos;t know without continuous monitoring. Turns out, sometimes he&apos;d be awake for 10-15 minutes before making a sound—information that changed how we thought about his sleep patterns entirely.&lt;/p&gt;
&lt;h3&gt;The uncomfortable truth&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The data revealed something important: &lt;em&gt;we&lt;/em&gt; were part of the problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The visualizations showed us patterns we hadn&apos;t noticed... how our decisions sometimes made things worse, not better. Our toddler still doesn&apos;t sleep through the night every time (sigh), but the improvement over the past few weeks has been undeniable. We&apos;re making data-driven adjustments instead of guessing, and it&apos;s working.&lt;/p&gt;
&lt;p&gt;Of course AI alone did &lt;strong&gt;not&lt;/strong&gt; solve our toddler&apos;s sleep issues—we did. But having accurate data made it way easier to identify what worked and what didn&apos;t.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This project started as a sleep-deprived experiment and turned into something genuinely useful. By combining off-the-shelf tools like Frigate and BabyBuddy with a custom-trained model and Claude as my coding assistant, I built a fully automated sleep tracking system in just a few days prompts.&lt;/p&gt;
&lt;p&gt;Would I recommend this approach? If you&apos;re comfortable with self-hosting and have a camera already set up, absolutely. The time investment paid for itself within the first week of not having to manually log anything.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Turns out, the best parenting tool is one that works while you&apos;re too tired to think.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>claude-code</category><category>sleep-deprivation</category><category>toddler</category><category>ai</category><category>babybuddy</category><category>frigate</category><category>self-hosting</category></item><item><title>eNSPanel: Building a Better Smart Home Touch Panel</title><link>https://andreabaccega.com/blog/enspanel-custom-nspanel-pcb/</link><guid isPermaLink="true">https://andreabaccega.com/blog/enspanel-custom-nspanel-pcb/</guid><description>An open-source custom PCB replacement for the Sonoff NSPanel that adds presence detection, voice assistant capabilities, and modular extensibility.</description><pubDate>Sun, 04 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Sonoff NSPanel is an affordable smart home touch panel that includes a capacitive display, two physical buttons, a buzzer, two relays, and WiFi/Bluetooth connectivity. It&apos;s a compelling device at its price point, but the stock firmware and hardware have significant limitations. That&apos;s why I created &lt;a href=&quot;https://github.com/vekexasia/eNSPanel&quot;&gt;eNSPanel&lt;/a&gt; — a custom PCB replacement that addresses these constraints while adding powerful new capabilities.&lt;/p&gt;
&lt;div&gt;
  &lt;img src=&quot;/blog/sonoff-nspanel-us-cropped.webp&quot; alt=&quot;Sonoff NSPanel US&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;And here&apos;s the custom eNSPanel PCB that replaces the original internals:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/enspanel-all-pcbs.png&quot; alt=&quot;eNSPanel PCB versions&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Why Replace the Original PCB?&lt;/h2&gt;
&lt;p&gt;The original NSPanel hardware presents several challenges:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Limited memory&lt;/strong&gt; — The stock ESP32 module lacks sufficient memory for advanced ESPHome features, making TFT uploads problematic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No presence detection&lt;/strong&gt; — You can&apos;t trigger automations based on room occupancy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No voice capabilities&lt;/strong&gt; — No microphone for voice assistant integration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No extensibility&lt;/strong&gt; — The closed design prevents adding custom sensors or modules&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;What eNSPanel Offers&lt;/h2&gt;
&lt;p&gt;The custom PCB maintains full compatibility with the original NSPanel enclosure while introducing substantial upgrades:&lt;/p&gt;
&lt;h3&gt;Upgraded ESP32-S3 Module&lt;/h3&gt;
&lt;p&gt;The heart of the upgrade is the ESP32-S3-WROOM-1-N16R8 module. This provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;16MB flash and 8MB PSRAM&lt;/li&gt;
&lt;li&gt;Sufficient memory for on-device wake word detection&lt;/li&gt;
&lt;li&gt;Better processing power for complex automations&lt;/li&gt;
&lt;li&gt;Full ESPHome compatibility without memory constraints&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;LD2410 Presence Sensor&lt;/h3&gt;
&lt;p&gt;The board integrates an LD2410 mmWave radar sensor for true presence detection. Unlike PIR sensors that only detect movement, mmWave radar can detect stationary occupants — perfect for triggering automations when someone is sitting still in a room.&lt;/p&gt;
&lt;h3&gt;Built-in Microphone&lt;/h3&gt;
&lt;p&gt;With the ESP32-S3&apos;s PSRAM, the eNSPanel supports on-device wake word recognition. Say &quot;Hey Jarvis&quot; (or configure your preferred wake word), and trigger your voice assistant without cloud dependencies. This enables distributed voice control throughout your home.&lt;/p&gt;
&lt;h3&gt;Modular Daughter Board System&lt;/h3&gt;
&lt;p&gt;Perhaps the most interesting design decision is the mother-daughter board architecture. The main PCB exposes 6 GPIO pins plus 5V and 3.3V power through a 10-pin connector. This allows you to create custom expansion boards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a ZigBee coordinator&lt;/li&gt;
&lt;li&gt;Include a speaker for audio feedback&lt;/li&gt;
&lt;li&gt;Integrate additional sensors&lt;/li&gt;
&lt;li&gt;Connect any peripheral your project requires&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The daughter board constraints are well-defined: max 34.6×51.4mm, max 1.6mm thickness, with specific clearances documented for enclosure compatibility.&lt;/p&gt;
&lt;h2&gt;Design Philosophy&lt;/h2&gt;
&lt;p&gt;All enhanced features are optional. Don&apos;t need presence detection? Skip the LD2410. Don&apos;t want voice control? Omit the microphone. The base PCB maintains all original NSPanel functionality — display, buzzer, thermistor, buttons, and relays — so you can start simple and expand later.&lt;/p&gt;
&lt;p&gt;The PCB dimensions (under 100×100mm) were intentionally chosen to qualify for manufacturing discounts at most PCB fabrication houses, keeping costs reasonable.&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/vekexasia/eNSPanel&quot;&gt;project repository&lt;/a&gt; includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gerber files&lt;/strong&gt; — Ready to upload to JLCPCB, PCBWay, or your preferred manufacturer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3D models&lt;/strong&gt; — For visualizing the build and checking enclosure fit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPHome configurations&lt;/strong&gt; — Modular YAML files for each component (display, buzzer, presence sensor, microphone, etc.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nextion display files&lt;/strong&gt; — Custom HMI for the touch interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complete documentation&lt;/strong&gt; — From ordering PCBs through soldering and firmware flashing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Technical Highlights&lt;/h2&gt;
&lt;p&gt;The ESPHome configuration is split into logical modules:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Core configuration
packages:
  core: !include esphome/core.yaml
  buzzer: !include esphome/buzzer.yaml
  nextion: !include esphome/nextion.yaml
  ld2410: !include esphome/ld2410.yaml      # Optional
  microphone: !include esphome/microphone.yaml  # Optional
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For voice assistant functionality, the configuration leverages ESPHome&apos;s &lt;code&gt;micro_wake_word&lt;/code&gt; component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;micro_wake_word:
  model: hey_jarvis
  on_wake_word_detected:
    - voice_assistant.start:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the wake word is detected, the system plays an acknowledgment tone and initiates the voice assistant pipeline — all processed locally on the ESP32-S3.&lt;/p&gt;
&lt;h2&gt;Who Is This For?&lt;/h2&gt;
&lt;p&gt;The eNSPanel is ideal if you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run Home Assistant with ESPHome&lt;/li&gt;
&lt;li&gt;Want room-level presence detection&lt;/li&gt;
&lt;li&gt;Desire local voice control without cloud dependencies&lt;/li&gt;
&lt;li&gt;Need a platform you can extend and customize&lt;/li&gt;
&lt;li&gt;Enjoy hardware projects and aren&apos;t afraid of SMD soldering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&apos;re comfortable ordering custom PCBs and doing surface-mount soldering (or using a reflow oven), this project transforms an inexpensive commercial device into a powerful smart home control point.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The Sonoff NSPanel provides excellent hardware at a low price, but its software and expansion options are limited. The eNSPanel project liberates that hardware, adding presence detection, voice capabilities, and infinite extensibility through the daughter board system — all while maintaining compatibility with the original enclosure.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://github.com/vekexasia/eNSPanel&quot;&gt;GitHub repository&lt;/a&gt; and &lt;a href=&quot;https://enspanel-docs.andreabaccega.com/&quot;&gt;documentation&lt;/a&gt; to get started with your own build.&lt;/p&gt;
</content:encoded><category>hardware</category><category>esp32</category><category>esphome</category><category>smart-home</category><category>open-source</category></item><item><title>Adding a Footer to NavigationView in Android</title><link>https://andreabaccega.com/blog/android-navigationview-footer/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-navigationview-footer/</guid><description>A workaround for adding footer views to Android&apos;s NavigationView component, which only officially supports headers.</description><pubDate>Fri, 28 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Android&apos;s NavigationView simplifies creating navigation drawer menus, but it has a limitation: you can only add headers and menu items - no footer support.&lt;/p&gt;
&lt;h2&gt;Understanding the Internal Structure&lt;/h2&gt;
&lt;p&gt;NavigationView internally uses a ListView (specifically &lt;code&gt;NavigationMenuView&lt;/code&gt;) to display menu items. Headers are added via &lt;code&gt;ListView.addHeaderView()&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Since it&apos;s a ListView under the hood, we can use &lt;code&gt;ListView.addFooterView()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        NavigationView navigationView = findViewById(R.id.nav_view);

        // Get the internal ListView
        NavigationMenuView navMenuView = (NavigationMenuView)
            navigationView.getChildAt(0);

        // Create and add footer
        View footer = getLayoutInflater()
            .inflate(R.layout.nav_footer, navMenuView, false);

        navMenuView.addFooterView(footer);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Important Caveat&lt;/h2&gt;
&lt;p&gt;This worked in early versions of the Support Library, but &lt;strong&gt;Google later replaced ListView with RecyclerView&lt;/strong&gt; in &lt;code&gt;NavigationMenuView&lt;/code&gt; (around version 23.1.1+).&lt;/p&gt;
&lt;p&gt;For newer versions, you&apos;ll need a different approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a custom NavigationView implementation&lt;/li&gt;
&lt;li&gt;Add the footer as the last menu item with custom styling&lt;/li&gt;
&lt;li&gt;Use a completely custom navigation drawer layout&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;When to Use This&lt;/h2&gt;
&lt;p&gt;This workaround is useful for older projects or when you have specific version constraints. For new projects, consider designing your navigation without relying on internal implementation details.&lt;/p&gt;
</content:encoded><category>android</category><category>java</category><category>ui</category><category>material-design</category></item><item><title>Fixing Fragment Overlap in Android&apos;s Back Stack</title><link>https://andreabaccega.com/blog/android-fragment-overlap/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-fragment-overlap/</guid><description>Understanding and solving the frustrating issue of fragments overlapping when managing the back stack in Android.</description><pubDate>Sun, 16 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Fragment overlap is one of those Android bugs that makes you question your sanity. Two fragments visible at the same time, stacked on top of each other. Here&apos;s what causes it and how to fix it.&lt;/p&gt;
&lt;h2&gt;The Scenario&lt;/h2&gt;
&lt;p&gt;You want to navigate A → B → C, but skip B from the back stack so pressing back from C returns directly to A.&lt;/p&gt;
&lt;p&gt;The intuitive approach is to skip &lt;code&gt;addToBackStack()&lt;/code&gt; on the B→C transaction:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// A → B (add to back stack)
fragmentManager.beginTransaction()
    .replace(R.id.container, fragmentB)
    .addToBackStack(null)
    .commit();

// B → C (skip back stack - THIS CAUSES PROBLEMS)
fragmentManager.beginTransaction()
    .replace(R.id.container, fragmentC)
    .commit();  // No addToBackStack()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Why This Breaks&lt;/h2&gt;
&lt;p&gt;When you press back from C, the fragment manager tries to reverse the last back stack entry. But that entry knows about A→B, not B→C.&lt;/p&gt;
&lt;p&gt;The reverse operation attempts to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove B (which isn&apos;t displayed!)&lt;/li&gt;
&lt;li&gt;Re-add A&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Result: Both A and C are visible simultaneously.&lt;/p&gt;
&lt;h2&gt;The Root Cause&lt;/h2&gt;
&lt;p&gt;The fragment manager maintains an internal list of fragments. When back stack operations reverse without proper tracking, fragments get duplicated in this list. Multiple fragments with the same container ID = overlap.&lt;/p&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Always add transactions to the back stack&lt;/strong&gt;, then handle custom navigation logic manually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Always add to back stack
fragmentManager.beginTransaction()
    .replace(R.id.container, fragmentC)
    .addToBackStack(&quot;C&quot;)
    .commit();

// Handle custom back navigation
getSupportFragmentManager().addOnBackStackChangedListener(() -&amp;gt; {
    int count = fragmentManager.getBackStackEntryCount();
    if (count &amp;gt; 0) {
        String name = fragmentManager.getBackStackEntryAt(count - 1).getName();
        if (&quot;B&quot;.equals(name)) {
            // Skip B, pop again
            fragmentManager.popBackStackImmediate();
        }
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Key Takeaway&lt;/h2&gt;
&lt;p&gt;Don&apos;t fight the fragment manager&apos;s internal state. Let it track everything, then use &lt;code&gt;popBackStackImmediate()&lt;/code&gt; and listeners to implement custom navigation flows. Your fragments will thank you.&lt;/p&gt;
</content:encoded><category>android</category><category>java</category><category>fragments</category><category>ui</category></item><item><title>Detecting Phones, Tablets, and Google TV in Android</title><link>https://andreabaccega.com/blog/android-device-detection/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-device-detection/</guid><description>How to detect whether your Android app is running on a phone, tablet, or Google TV device using utility methods from Google&apos;s IO Sched app.</description><pubDate>Fri, 24 Aug 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building an Android app, you face a fundamental decision: create a single app version supporting multiple device types, or upload separate optimized versions to Google Play Store.&lt;/p&gt;
&lt;h2&gt;Single App vs Multiple Apps&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Single App Approach:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Less code to maintain&lt;/li&gt;
&lt;li&gt;✅ Greater scalability&lt;/li&gt;
&lt;li&gt;❌ Requires device-specific routing logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Multiple App Approach:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Google Play handles filtering upfront&lt;/li&gt;
&lt;li&gt;❌ Bug fixes require multiple app updates&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Solution: Utility Methods&lt;/h2&gt;
&lt;p&gt;Google&apos;s IO Sched application provides an excellent example of the single-app approach. Here are the key utility methods for device detection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class UIUtils {

    public static boolean hasHoneycomb() {
        return Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.HONEYCOMB;
    }

    public static boolean isTablet(Context context) {
        return (context.getResources().getConfiguration().screenLayout
                &amp;amp; Configuration.SCREENLAYOUT_SIZE_MASK)
                &amp;gt;= Configuration.SCREENLAYOUT_SIZE_LARGE;
    }

    public static boolean isHoneycombTablet(Context context) {
        return hasHoneycomb() &amp;amp;&amp;amp; isTablet(context);
    }

    public static boolean isGoogleTV(Context context) {
        return context.getPackageManager()
                .hasSystemFeature(&quot;com.google.android.tv&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Practical Implementation&lt;/h2&gt;
&lt;p&gt;Using the Facade pattern, you can conditionally load different activities based on device type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Intent intent;
if (UIUtils.isTablet(this)) {
    intent = new Intent(this, MapMultiPaneActivity.class);
} else {
    intent = new Intent(this, MapActivity.class);
}
startActivity(intent);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach lets you maintain a single codebase while still providing optimized experiences for different device form factors.&lt;/p&gt;
&lt;h2&gt;Credit&lt;/h2&gt;
&lt;p&gt;These utility methods are derived from Google&apos;s open-source IO Sched application code.&lt;/p&gt;
</content:encoded><category>android</category><category>java</category><category>device-detection</category></item><item><title>Android Form Validation with FormEditText</title><link>https://andreabaccega.com/blog/android-edittext-validation/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-edittext-validation/</guid><description>Introducing FormEditText - an open-source library that brings easy data validation to Android EditText fields through XML attributes.</description><pubDate>Sat, 26 May 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Input validation can be painful. Building forms in any technology is a boring and tedious job, yet it&apos;s one of the most common tasks in app development.&lt;/p&gt;
&lt;p&gt;When implementing form validation, we typically face three critical concerns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;In-depth data validation&lt;/strong&gt; - Ensuring the data format is correct&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mandatory fields checking&lt;/strong&gt; - Making sure required fields aren&apos;t empty&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error presentation&lt;/strong&gt; - Showing users what went wrong&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Introducing FormEditText&lt;/h2&gt;
&lt;p&gt;To solve these pain points, I created an open-source library called &lt;a href=&quot;https://github.com/vekexasia/android-edittext-validator&quot;&gt;FormEditText&lt;/a&gt;, available on GitHub.&lt;/p&gt;
&lt;p&gt;The library enables developers to validate EditText fields through simple XML attributes, eliminating boilerplate validation code.&lt;/p&gt;
&lt;h3&gt;Basic Usage&lt;/h3&gt;
&lt;p&gt;First, define a custom namespace in your layout and add validation attributes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;com.andreabaccega.widget.FormEditText
    android:id=&quot;@+id/email&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:inputType=&quot;textEmailAddress&quot;
    app:testType=&quot;email&quot;
    app:emptyAllowed=&quot;false&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Validation Behavior&lt;/h3&gt;
&lt;p&gt;When you call &lt;code&gt;validate()&lt;/code&gt; on a FormEditText:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Valid input&lt;/strong&gt;: Returns &lt;code&gt;true&lt;/code&gt; without additional action&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invalid input&lt;/strong&gt;: Returns &lt;code&gt;false&lt;/code&gt; and automatically displays an error message&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Built-in Validators&lt;/h3&gt;
&lt;p&gt;The library includes validators for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email addresses&lt;/li&gt;
&lt;li&gt;Phone numbers&lt;/li&gt;
&lt;li&gt;Credit cards&lt;/li&gt;
&lt;li&gt;IP addresses&lt;/li&gt;
&lt;li&gt;Web URLs&lt;/li&gt;
&lt;li&gt;Custom regex patterns&lt;/li&gt;
&lt;li&gt;And more...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Try It Out&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vekexasia/android-edittext-validator&quot;&gt;Source code on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Example application available on Google Play&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The library has grown to over 1,400 stars on GitHub and continues to help Android developers build better forms.&lt;/p&gt;
</content:encoded><category>android</category><category>java</category><category>validation</category><category>open-source</category></item><item><title>Introducing Flatterizor: Storing Complex Objects in Databases</title><link>https://andreabaccega.com/blog/flatterizor-php-library/</link><guid isPermaLink="true">https://andreabaccega.com/blog/flatterizor-php-library/</guid><description>A PHP library that flattens complex nested objects for database storage while maintaining SQL queryability.</description><pubDate>Wed, 09 May 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Standard PHP serialization fails when you need to store complex objects in a database AND query them with SQL. Enter Flatterizor.&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Imagine you have a user object with nested permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$user = [
    &apos;name&apos; =&amp;gt; &apos;John&apos;,
    &apos;permissions&apos; =&amp;gt; [
        &apos;can_read&apos; =&amp;gt; true,
        &apos;can_write&apos; =&amp;gt; false,
        &apos;can_delete&apos; =&amp;gt; true
    ]
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;serialize()&lt;/code&gt; produces output like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a:2:{s:4:&quot;name&quot;;s:4:&quot;John&quot;;s:11:&quot;permissions&quot;;a:3:{...}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now try to write a SQL query to find all users with &lt;code&gt;can_write = true&lt;/code&gt;. Nearly impossible.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Flatterizor converts complex objects into a normalized structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Input
$user = [&apos;name&apos; =&amp;gt; &apos;John&apos;, &apos;permissions&apos; =&amp;gt; [&apos;can_write&apos; =&amp;gt; true]];

// Output: flattened key-value pairs
[
    [&apos;path&apos; =&amp;gt; &apos;name&apos;, &apos;value&apos; =&amp;gt; &apos;John&apos;],
    [&apos;path&apos; =&amp;gt; &apos;permissions.can_write&apos;, &apos;value&apos; =&amp;gt; true]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Database Implementation&lt;/h2&gt;
&lt;p&gt;Create a table with path/value columns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE user_meta (
    user_id INT,
    name VARCHAR(255),  -- the property path
    val TEXT            -- the property value
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can easily query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT DISTINCT user_id FROM user_meta
WHERE name = &apos;permissions.can_write&apos; AND val = &apos;1&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Real-World Use Case&lt;/h2&gt;
&lt;p&gt;This library emerged from WordPress development needs. I had forms with ~30 fields that needed to be stored as post metadata while remaining searchable.&lt;/p&gt;
&lt;p&gt;Instead of creating 30 separate meta keys, Flatterizor handles the complexity automatically while keeping everything queryable.&lt;/p&gt;
&lt;h2&gt;Get Started&lt;/h2&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://github.com/vekexasia/Flatterizor&quot;&gt;GitHub repository&lt;/a&gt; for documentation and examples.&lt;/p&gt;
</content:encoded><category>php</category><category>database</category><category>wordpress</category><category>open-source</category></item><item><title>How to Backup Your Web Server with rsnapshot, rsync, and SSH</title><link>https://andreabaccega.com/blog/server-backup-rsnapshot/</link><guid isPermaLink="true">https://andreabaccega.com/blog/server-backup-rsnapshot/</guid><description>A comprehensive guide to implementing secure, automated server backups using rsnapshot, rsync, and SSH with proper security practices.</description><pubDate>Mon, 23 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Backups become critical when data loss occurs or when you&apos;re hosting third-party information. Here&apos;s a battle-tested approach using rsnapshot, rsync, and SSH.&lt;/p&gt;
&lt;h2&gt;Key Questions to Answer First&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Why?&lt;/strong&gt; Data protection and minimizing potential losses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What?&lt;/strong&gt; Configuration files (Apache, MySQL, Nginx), www directory, databases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Where?&lt;/strong&gt; From same drive to geographically distributed servers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When?&lt;/strong&gt; Depends on your data change frequency&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Core Principles&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The source server should not control backups&lt;/strong&gt; - It should only expose data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backup servers should pull, not receive pushes&lt;/strong&gt; - Prevents compromised servers from accessing backups&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use existing, proven tools&lt;/strong&gt; - Don&apos;t reinvent the wheel&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Step 1: MySQL Backup Setup&lt;/h2&gt;
&lt;p&gt;Create a dedicated backup user with limited permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRANT SELECT, LOCK TABLES ON *.* TO &apos;backup&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;password&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a credentials file at &lt;code&gt;~/.my.cnf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[mysqldump]
user=backup
password=yourpassword
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Secure it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod 700 ~/.my.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add to crontab for daily 1:10 AM backups:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 1 * * * mysqldump --all-databases &amp;gt; /backup/mysql/$(date +\%Y\%m\%d).sql
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2: SSH Key Setup&lt;/h2&gt;
&lt;p&gt;Generate a dedicated key pair on the backup server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen -t rsa -b 4096 -f ~/.ssh/backup_key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the source server, add the public key with restrictions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# In ~/.ssh/authorized_keys
from=&quot;BACKUP_SERVER_IP&quot;,no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAAA...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 3: Rsnapshot Configuration&lt;/h2&gt;
&lt;p&gt;Install rsync on both servers, rsnapshot on the backup server.&lt;/p&gt;
&lt;p&gt;Configure &lt;code&gt;/etc/rsnapshot.conf&lt;/code&gt; (use tabs, not spaces!):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;snapshot_root   /backup/snapshots/

retain  hourly  6
retain  daily   7
retain  weekly  4
retain  monthly 12

backup  backupper@source:/var/www/    www/
backup  backupper@source:/etc/        etc/
backup  backupper@source:/backup/mysql/   mysql/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 4: Sudoers for rsync&lt;/h2&gt;
&lt;p&gt;Allow the backup user to run rsync with elevated privileges:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# In /etc/sudoers
backupper ALL= NOPASSWD: /usr/bin/rsync
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Why This Works&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Incremental backups&lt;/strong&gt; - Only changed files are transferred&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard-link deduplication&lt;/strong&gt; - Unchanged files don&apos;t consume extra space&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pull-based security&lt;/strong&gt; - Compromised source can&apos;t delete backups&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proven tools&lt;/strong&gt; - rsnapshot and rsync are battle-tested&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setup has protected my servers for years with minimal maintenance overhead.&lt;/p&gt;
</content:encoded><category>devops</category><category>linux</category><category>backup</category><category>security</category></item><item><title>Execute Code Only on First App Launch in Android</title><link>https://andreabaccega.com/blog/android-first-launch-code/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-first-launch-code/</guid><description>A simple pattern using SharedPreferences to run initialization code only the first time your Android app is launched.</description><pubDate>Thu, 12 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A common Android development challenge: executing code exclusively during an application&apos;s initial launch. A practical use case is displaying a tutorial or onboarding flow only when users first open the app.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Leverage &lt;code&gt;SharedPreferences&lt;/code&gt; to track whether the app has been launched before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MainActivity extends Activity {

    private static final String PREFS_NAME = &quot;MyPrefs&quot;;
    private static final String FIRST_LAUNCH_KEY = &quot;firstLaunch&quot;;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (isFirstLaunch()) {
            // Your first-launch code here
            showTutorial();
            markFirstLaunchComplete();
        }
    }

    private boolean isFirstLaunch() {
        SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
        return prefs.getBoolean(FIRST_LAUNCH_KEY, true);
    }

    private void markFirstLaunchComplete() {
        SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
        prefs.edit()
             .putBoolean(FIRST_LAUNCH_KEY, false)
             .apply();
    }

    private void showTutorial() {
        // Display your onboarding UI
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How It Works&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Check the &lt;code&gt;SharedPreferences&lt;/code&gt; for a boolean flag&lt;/li&gt;
&lt;li&gt;If the flag doesn&apos;t exist (or is &lt;code&gt;true&lt;/code&gt;), it&apos;s the first launch&lt;/li&gt;
&lt;li&gt;Run your initialization code&lt;/li&gt;
&lt;li&gt;Set the flag to &lt;code&gt;false&lt;/code&gt; to prevent future execution&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Important Caveat&lt;/h2&gt;
&lt;p&gt;This approach resets when the app is uninstalled, since &lt;code&gt;SharedPreferences&lt;/code&gt; data is cleared with the app. If you need persistence across installs, consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Storing the flag on a server&lt;/li&gt;
&lt;li&gt;Using device identifiers (with appropriate privacy considerations)&lt;/li&gt;
&lt;li&gt;Using Android&apos;s Backup API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For most use cases like tutorials and onboarding, resetting on reinstall is actually the desired behavior.&lt;/p&gt;
</content:encoded><category>android</category><category>java</category><category>patterns</category></item><item><title>Password Reset: A Modern Design Pattern</title><link>https://andreabaccega.com/blog/password-reset-pattern/</link><guid isPermaLink="true">https://andreabaccega.com/blog/password-reset-pattern/</guid><description>How to implement a secure password reset flow that protects users even if their email is compromised.</description><pubDate>Sat, 07 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let&apos;s talk about password recovery mechanisms - specifically, why some old approaches are dangerous and what the modern best practice looks like.&lt;/p&gt;
&lt;h2&gt;The Bad Old Days&lt;/h2&gt;
&lt;p&gt;Insecure patterns from the past included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plain text storage&lt;/strong&gt; - Allowing users to retrieve their original passwords&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Symmetric encryption&lt;/strong&gt; - Same problem, just with extra steps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security questions&lt;/strong&gt; - &quot;What&apos;s your mother&apos;s maiden name?&quot; (easily guessable or social-engineerable)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Modern Approach: Password Reset&lt;/h2&gt;
&lt;p&gt;Instead of retrieving passwords, implement a secure reset flow:&lt;/p&gt;
&lt;h3&gt;The Process&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;User requests password reset via username or email&lt;/li&gt;
&lt;li&gt;Application generates a unique, cryptographically random token&lt;/li&gt;
&lt;li&gt;Token is stored in the database with an expiration timestamp&lt;/li&gt;
&lt;li&gt;User receives an email with a reset link containing username and token&lt;/li&gt;
&lt;li&gt;Application validates the request&lt;/li&gt;
&lt;li&gt;User sets a new password&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Validation Checklist&lt;/h3&gt;
&lt;p&gt;Before allowing a password reset, verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Valid username/token pairing&lt;/li&gt;
&lt;li&gt;✅ Token hasn&apos;t expired (typically 24-hour window)&lt;/li&gt;
&lt;li&gt;✅ Token hasn&apos;t been previously used (one-time use)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Why Not Send Auto-Generated Passwords?&lt;/h3&gt;
&lt;p&gt;Think about what happens if an attacker gains access to someone&apos;s email. They could:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request password resets for various services&lt;/li&gt;
&lt;li&gt;Search for &quot;password&quot; in the email archive&lt;/li&gt;
&lt;li&gt;Access accounts using the auto-generated passwords found&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By requiring users to actively set their new password on your site, you add an extra layer of protection.&lt;/p&gt;
&lt;h2&gt;Implementation Tips&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE password_resets (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    token VARCHAR(64) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    used_at TIMESTAMP NULL,
    expires_at TIMESTAMP NOT NULL
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always hash the token before storing it (just like passwords), and invalidate it immediately after use.&lt;/p&gt;
</content:encoded><category>security</category><category>authentication</category><category>patterns</category></item><item><title>Preventing XSS While Allowing Some HTML Tags in PHP</title><link>https://andreabaccega.com/blog/xss-prevention-php/</link><guid isPermaLink="true">https://andreabaccega.com/blog/xss-prevention-php/</guid><description>Why strip_tags() isn&apos;t safe enough and how to properly sanitize HTML when you need to whitelist certain tags.</description><pubDate>Fri, 23 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;PHP&apos;s &lt;code&gt;strip_tags()&lt;/code&gt; function seems like an easy solution for allowing certain HTML elements while blocking others. But it has a dangerous flaw.&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;strip_tags()&lt;/code&gt; accepts a second parameter to whitelist specific tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$clean = strip_tags($input, &apos;&amp;lt;a&amp;gt;&amp;lt;b&amp;gt;&amp;lt;i&amp;gt;&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks safe, right? But what if the input contains:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;javascript:alert(&apos;XSS&apos;)&quot; onclick=&quot;stealCookies()&quot;&amp;gt;Click me&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag is whitelisted, so it passes through - complete with its malicious attributes. &lt;code&gt;strip_tags()&lt;/code&gt; removes tags, but &lt;strong&gt;preserves their attributes&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;If you don&apos;t need attributes, strip them entirely:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function strip_tags_with_attributes($string, $allowedTags) {
    // First, strip disallowed tags
    $string = strip_tags($string, $allowedTags);

    // Then remove all attributes from allowed tags
    return preg_replace(&apos;/&amp;lt;(\w+)[^&amp;gt;]*&amp;gt;/&apos;, &apos;&amp;lt;$1&amp;gt;&apos;, $string);
}

// Usage
$input = &apos;&amp;lt;a href=&quot;javascript:bad()&quot; onclick=&quot;evil()&quot;&amp;gt;Link&amp;lt;/a&amp;gt;&apos;;
$clean = strip_tags_with_attributes($input, &apos;&amp;lt;a&amp;gt;&amp;lt;b&amp;gt;&amp;lt;i&amp;gt;&apos;);
// Result: &amp;lt;a&amp;gt;Link&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;When This Isn&apos;t Enough&lt;/h2&gt;
&lt;p&gt;If you actually need to preserve safe attributes (like &lt;code&gt;href&lt;/code&gt; for links), you need a proper HTML sanitizer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTMLPurifier&lt;/strong&gt; - The gold standard for PHP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DOMDocument&lt;/strong&gt; - Parse and whitelist specific attributes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Key Takeaway&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;strip_tags()&lt;/code&gt; alone is &lt;strong&gt;not safe enough&lt;/strong&gt; when you&apos;re whitelisting tags. Always consider what attributes could slip through and either:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove all attributes (simple approach above)&lt;/li&gt;
&lt;li&gt;Use a proper HTML sanitization library (complex but flexible)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Never trust user input, even when it appears to be sanitized.&lt;/p&gt;
</content:encoded><category>security</category><category>php</category><category>xss</category></item><item><title>Handling onChange Events on EditText in Android</title><link>https://andreabaccega.com/blog/android-edittext-onchange/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-edittext-onchange/</guid><description>How to implement real-time change detection for Android&apos;s EditText widget using TextWatcher, similar to JavaScript&apos;s onChange event.</description><pubDate>Sat, 09 Oct 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you&apos;re coming from web development, you might be looking for something similar to HTML/JavaScript&apos;s &lt;code&gt;onChange&lt;/code&gt; event for Android&apos;s EditText widget. Here&apos;s how to implement real-time change detection.&lt;/p&gt;
&lt;h2&gt;Common Use Cases&lt;/h2&gt;
&lt;p&gt;Monitoring EditText changes is useful for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Character count display&lt;/strong&gt; - Showing users how many characters they&apos;ve typed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remaining characters&lt;/strong&gt; - Like Twitter&apos;s character limit countdown&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live processing&lt;/strong&gt; - Sending partial input to a server for autocomplete suggestions&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Solution: TextWatcher&lt;/h2&gt;
&lt;p&gt;Android provides the &lt;code&gt;TextWatcher&lt;/code&gt; interface for monitoring text changes. Register it with your EditText using &lt;code&gt;addTextChangedListener()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EditText editText = findViewById(R.id.et_text);
TextView charCount = findViewById(R.id.char_count);

editText.addTextChangedListener(new TextWatcher() {

    @Override
    public void afterTextChanged(Editable s) {
        // Called after the text has changed
        charCount.setText(String.format(&quot;%d characters&quot;, s.length()));
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // Called before the text is changed
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // Called as the text is being changed
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Which Callback to Use?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;beforeTextChanged&lt;/code&gt;&lt;/strong&gt; - Called before changes are applied. Useful for capturing the previous state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;onTextChanged&lt;/code&gt;&lt;/strong&gt; - Called during the change. You get info about what&apos;s being added/removed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;afterTextChanged&lt;/code&gt;&lt;/strong&gt; - Called after changes are complete. Best for most use cases like updating UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For simple scenarios like character counting, &lt;code&gt;afterTextChanged&lt;/code&gt; is usually what you need. The &lt;code&gt;Editable&lt;/code&gt; parameter gives you direct access to the current text content.&lt;/p&gt;
</content:encoded><category>android</category><category>java</category><category>ui</category></item><item><title>Android Root Certificate Authorities List</title><link>https://andreabaccega.com/blog/android-ssl-certificates/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-ssl-certificates/</guid><description>How to extract and view the trusted Certificate Authorities (CAs) from an Android device using BouncyCastle and ADB.</description><pubDate>Thu, 23 Sep 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ever wondered which Certificate Authorities are trusted by Android? Here&apos;s how to extract the root CA list from an Android device.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Download the &lt;a href=&quot;https://www.bouncycastle.org/java.html&quot;&gt;BouncyCastle provider JAR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Place it in &lt;code&gt;$JAVA_HOME/lib/ext&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Ensure &lt;code&gt;$JAVA_HOME/bin&lt;/code&gt; is in your system PATH&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Extracting the Certificates&lt;/h2&gt;
&lt;p&gt;Pull the certificate store from your device and extract the list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Pull the certificate store from the device
adb pull /system/etc/security/cacerts.bks cacerts.bks

# Extract certificate information
keytool -keystore cacerts.bks \
        -storetype BKS \
        -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
        -storepass changeit \
        -list -v &amp;gt;&amp;gt; certificates.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What You&apos;ll Find&lt;/h2&gt;
&lt;p&gt;Android 2.2 (Froyo) included 57 trusted certificate entries from authorities including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VeriSign&lt;/li&gt;
&lt;li&gt;DigiCert&lt;/li&gt;
&lt;li&gt;Equifax&lt;/li&gt;
&lt;li&gt;StartCom&lt;/li&gt;
&lt;li&gt;GlobalSign&lt;/li&gt;
&lt;li&gt;Thawte&lt;/li&gt;
&lt;li&gt;GeoTrust&lt;/li&gt;
&lt;li&gt;And many others from various countries&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;macOS Note&lt;/h2&gt;
&lt;p&gt;On macOS, you may need to use the full &lt;code&gt;keytool&lt;/code&gt; command path:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/Library/Java/JavaVirtualMachines/jdk-X.X.jdk/Contents/Home/bin/keytool
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Why This Matters&lt;/h2&gt;
&lt;p&gt;Understanding which CAs are trusted helps you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debug SSL/TLS certificate issues&lt;/li&gt;
&lt;li&gt;Understand the security implications of your app&apos;s connections&lt;/li&gt;
&lt;li&gt;Plan certificate pinning strategies&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>android</category><category>security</category><category>ssl</category></item><item><title>Adding Events to Google Calendar on Android</title><link>https://andreabaccega.com/blog/android-google-calendar-events/</link><guid isPermaLink="true">https://andreabaccega.com/blog/android-google-calendar-events/</guid><description>How to programmatically add calendar events on Android across different SDK versions, from 1.5 to Froyo and beyond.</description><pubDate>Mon, 09 Aug 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Google doesn&apos;t officially recommend using internal Calendar APIs, but sometimes there&apos;s no other way to achieve certain tasks. Here&apos;s how to add calendar events programmatically across different Android versions.&lt;/p&gt;
&lt;h2&gt;The Provider URI Challenge&lt;/h2&gt;
&lt;p&gt;The main challenge is that the Calendar content provider URI changed between Android versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pre-Froyo (&amp;lt; 2.2)&lt;/strong&gt;: &lt;code&gt;content://calendar/events&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Froyo and above (≥ 2.2)&lt;/strong&gt;: &lt;code&gt;content://com.android.calendar/events&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Here&apos;s a version-agnostic approach to adding calendar events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private Uri getCalendarUri() {
    if (Build.VERSION.SDK_INT &amp;gt;= 8) {
        return Uri.parse(&quot;content://com.android.calendar/events&quot;);
    }
    return Uri.parse(&quot;content://calendar/events&quot;);
}

public void addCalendarEvent(String title, String description,
                              long startTime, long endTime) {
    ContentResolver cr = getContentResolver();
    ContentValues values = new ContentValues();

    values.put(&quot;calendar_id&quot;, 1);
    values.put(&quot;title&quot;, title);
    values.put(&quot;description&quot;, description);
    values.put(&quot;dtstart&quot;, startTime);
    values.put(&quot;dtend&quot;, endTime);
    values.put(&quot;eventTimezone&quot;, TimeZone.getDefault().getID());

    Uri eventUri = cr.insert(getCalendarUri(), values);

    if (eventUri != null) {
        Log.d(&quot;Calendar&quot;, &quot;Event created: &quot; + eventUri.toString());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Required Permissions&lt;/h2&gt;
&lt;p&gt;Don&apos;t forget to add the calendar permissions to your &lt;code&gt;AndroidManifest.xml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;uses-permission android:name=&quot;android.permission.READ_CALENDAR&quot; /&amp;gt;
&amp;lt;uses-permission android:name=&quot;android.permission.WRITE_CALENDAR&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Important Notes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;This uses undocumented APIs that may change&lt;/li&gt;
&lt;li&gt;Always check for null returns from &lt;code&gt;insert()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Consider using the Calendar Intent for a more stable approach when user interaction is acceptable&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Intent-based approach is more reliable for user-facing features, but programmatic insertion is useful for background sync scenarios.&lt;/p&gt;
</content:encoded><category>android</category><category>java</category><category>calendar</category></item></channel></rss>