<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Antonio Gallo on Medium]]></title>
        <description><![CDATA[Stories by Antonio Gallo on Medium]]></description>
        <link>https://medium.com/@antoniogallo.it?source=rss-ab2ff435814d------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Ul2dqQy8UpjR0kZxM_V-Nw.jpeg</url>
            <title>Stories by Antonio Gallo on Medium</title>
            <link>https://medium.com/@antoniogallo.it?source=rss-ab2ff435814d------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 16:33:14 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@antoniogallo.it/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How I Succeded To Dictate AI Prompts Instead of Typing Them with Voxtype on Linux Mint without…]]></title>
            <link>https://medium.com/@antoniogallo.it/how-i-succeded-to-dictate-ai-prompts-instead-of-typing-them-with-voxtype-on-linux-mint-without-7811482e7861?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/7811482e7861</guid>
            <category><![CDATA[vibe-coding]]></category>
            <category><![CDATA[dictate]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Fri, 19 Jun 2026 12:25:14 GMT</pubDate>
            <atom:updated>2026-06-19T12:25:14.372Z</atom:updated>
            <content:encoded><![CDATA[<h3>How I Succeded To Dictate AI Prompts Instead of Typing Them with Voxtype on Linux Mint without Cloud Dictation</h3><p>A practical debugging story about <strong>push-to-talk speech-to-text</strong>, AI CLI, Cinnamon, X11, Whisper, dotool, ydotool, and the shortcuts I should have avoided.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7Gl5T3B3KoYhvyxA" /><figcaption>Image courtesy of Vanna Phon (@phonvanna)</figcaption></figure><h3>The Goal</h3><p><strong>I wanted a very simple thing: hold one key, speak a prompt, release the key, and have the text appear inside the terminal where I run AI CLI</strong>. Not a voice assistant.</p><p>Not a meeting transcription tool.</p><p>Not a cloud dictation product.</p><p><strong>Just push-to-talk for AI prompts</strong>.</p><p>The final result worked. But <strong>getting there involved a surprisingly long chain</strong> of Linux desktop details: X11 versus Wayland, clipboard backends, old ydotool syntax, a Voxtype upgrade, GPU acceleration getting disabled, systemd user services, and finally the boring truth that microphone quality matters.</p><h3>The Hardware Shortcut</h3><p>In my case the hotkey is F16.</p><p>On my keyboard, that is not labeled “F16”. It is the key with the “X” symbol in a group of four keys that look a bit like PlayStation-style symbols.</p><p>This is actually a good setup. Extra keyboard keys often emit F13 to F24, which are useful because they rarely conflict with editors, terminals, browsers, or desktop shortcuts.</p><h3>Finding Out I Had Already Started</h3><p>Before installing anything new, I searched my existing home directory for dictation-related tools and configuration. That turned out to be the right move.</p><p>I already had a Voxtype configuration:</p><pre>~/.config/voxtype/config.toml</pre><p>The important pieces were already there:</p><pre>[hotkey]<br>key = &quot;F16&quot;<br>mode = &quot;push_to_talk&quot;</pre><pre>[whisper]<br>model = &quot;base&quot;<br>language = &quot;it&quot;</pre><pre>[output]<br>mode = &quot;type&quot;</pre><p>So the problem was not starting from zero. The problem was making the setup reliable enough to use all day.</p><h3>The First Temptation: Paste Mode</h3><p>At first, I did not want Voxtype to type character by character. I wanted it to copy the transcription to the clipboard and paste it into the terminal in one shot.</p><p>This is because those AI CLI tool works better when you paste stuffs.</p><p>The attractive configuration was:</p><pre>[output]<br>mode = &quot;paste&quot;</pre><p>It looked cleaner. It sounded more robust. On Linux Mint Cinnamon, which is an X11 desktop, it turned into a long debugging session.</p><h3>Why Paste Was Harder Than Expected</h3><p>The generic error was not very helpful:</p><pre>Output failed: All output methods failed.<br>Ensure wtype, dotool, ydotool, wl-copy, or xclip is available.</pre><p>After running Voxtype with verbose logging, the real issue became clearer. On my system:</p><ul><li>wtype is useful on Wayland, not on my X11 session.</li><li>wl-copy is also Wayland-oriented.</li><li>xclip works for the X11 clipboard.</li><li>dotool can type text successfully.</li><li>ydotool exists, but the distro package did not match the syntax Voxtype wanted to use.</li></ul><p>One failed workaround was using mode = &quot;clipboard&quot; plus an external paste command. It did trigger paste, but it pasted the previous clipboard content instead of the fresh transcription.</p><blockquote>If your workaround pastes the previous clipboard value, it is not a solution. It is a race condition.</blockquote><h3>Upgrading Voxtype</h3><p>I was running Voxtype 0.6.2. A related X11 clipboard issue had already been closed upstream, so upgrading was the next reasonable step.</p><p>Since Voxtype was installed from a local .deb, a normal apt upgrade was not enough. I installed the newer package manually:</p><pre>cd ~/tmp</pre><pre>curl -A &#39;Mozilla/5.0&#39; -L \<br>  -o voxtype_0.7.5-1_amd64.deb \<br>  <a href="https://github.com/peteonrails/voxtype/releases/download/v0.7.5/voxtype_0.7.5-1_amd64.deb">https://github.com/peteonrails/voxtype/releases/download/v0.7.5/voxtype_0.7.5-1_amd64.deb</a></pre><pre>sudo apt install ./voxtype_0.7.5-1_amd64.deb</pre><pre>voxtype --version</pre><p>After the upgrade, Voxtype did improve its X11 behavior:</p><pre>Using xclip for X11 clipboard</pre><p>But paste mode still failed for me because of the next layer: ydotool.</p><h3>The ydotool Problem</h3><p>Voxtype 0.7.5 tried to send the paste shortcut like this:</p><pre>ydotool key 29:1 47:1 47:0 29:0</pre><p>On my system, the Ubuntu/Mint ydotool package interpreted that badly and produced:</p><pre>2442</pre><p>The same tool worked when called like this:</p><pre>ydotool key ctrl+v</pre><p>That told me the problem was not simply “ydotool is missing”. The problem was version and syntax compatibility.</p><p><strong>Constraint:</strong> I did not want to install random tools with Cargo or manually copied binaries. For this setup, I wanted packages or reversible local config only.</p><h3>The Practical Decision: Stop Chasing Paste</h3><p>At this point, paste mode had consumed enough time. The stable solution was to go back to typing output with dotool.</p><p>This is less elegant than a single atomic paste, but it works reliably for Codex CLI.</p><pre>[output]<br># 2026-06-19: paste mode is not reliable on this X11 setup; use dotool typing instead.<br>mode = &quot;type&quot;<br>fallback_to_clipboard = true<br>driver_order = [ &quot;dotool&quot; ]<br>type_delay_ms = 0</pre><p>This was the key practical lesson: for a terminal prompt, reliable simulated typing is better than fragile paste magic.</p><h3>The Upgrade Made Transcription Slow</h3><p>After upgrading Voxtype, transcription suddenly felt much slower. My first suspicion was that I had accidentally deleted or damaged the Whisper config.</p><p>I checked the configuration and it was still correct:</p><pre>[whisper]<br>model = &quot;base&quot;<br>language = &quot;it&quot;<br>translate = false</pre><p>The real issue was the backend. Before the upgrade, Voxtype was using the Vulkan binary. After the upgrade, the wrapper selected the CPU backend unless GPU mode was enabled again.</p><p>The useful check:</p><pre>voxtype setup gpu</pre><p>The desired output:</p><pre>Active backend: GPU (Vulkan)<br>Binary: /usr/lib/voxtype/voxtype-vulkan</pre><p>To enable GPU acceleration:</p><pre>sudo voxtype setup gpu --enable</pre><h3>Running Voxtype as a User Service</h3><p>Once the configuration worked, I enabled the user service:</p><pre>systemctl --user enable --now voxtype.service</pre><p>To check whether it is running:</p><pre>systemctl --user is-active voxtype.service<br>systemctl --user status voxtype.service --no-pager</pre><p>For live logs, this was the command that worked on my system:</p><pre>journalctl --user-unit=voxtype.service -f</pre><p>This one did not show logs for me:</p><pre>journalctl --user -u voxtype.service -f</pre><h3>Disabling Desktop Notifications</h3><p>Voxtype can show a desktop notification containing the transcribed text. That is useful while debugging, but distracting in daily use.</p><pre>[output.notification]<br>on_recording_start = false<br>on_recording_stop = false<br>on_transcription = false</pre><p>Then restart:</p><pre>systemctl --user restart voxtype.service</pre><h3>Accuracy: The Software Was Not the Whole Story</h3><p>Once the pipeline worked, I noticed that Italian recognition was still not perfect. For example:</p><pre>Real audio:     Però non mi sembra funzionare molto bene<br>Voxtype output: Per anno mi sembra funzionare molto bene</pre><p>The current model was base, which is fast but not always accurate on short Italian utterances. The first model upgrade to try is:</p><pre>voxtype setup --download --model small<br>voxtype setup model --set small --restart</pre><p>But if the microphone is bad, a bigger model will only help so much. Before blaming Whisper, record the actual microphone input:</p><pre>pactl list sources short</pre><pre>parecord --duration=5 /tmp/mic-test.wav<br>paplay /tmp/mic-test.wav</pre><p>If that recording sounds distant, noisy, clipped, or echoey, the transcription will suffer. This is especially true with a room microphone or the wrong default input device.</p><h3>Useful Commands</h3><h3>Voxtype status and configuration</h3><pre>voxtype --version<br>voxtype config<br>voxtype setup check<br>voxtype setup gpu<br>voxtype setup model --list</pre><h3>Manual debug mode</h3><pre>systemctl --user stop voxtype.service<br>voxtype -vv daemon</pre><h3>Service management</h3><pre>systemctl --user enable --now voxtype.service<br>systemctl --user restart voxtype.service<br>systemctl --user status voxtype.service --no-pager<br>journalctl --user-unit=voxtype.service -f</pre><h3>Input and backend checks</h3><pre>command -v dotool ydotool xclip wl-copy wtype<br>id<br>ls -al /dev/uinput<br>pactl list sources short</pre><h3>Clipboard and typing tests</h3><pre>printf &#39;TEST\n&#39; | xclip -selection clipboard<br>xclip -selection clipboard -o</pre><pre>sleep 3; printf &#39;type Test dotool\n&#39; | dotool<br>sleep 3; ydotool key ctrl+v</pre><h3>Tips and Tricks</h3><ul><li>Do not run voxtype daemon manually while the systemd user service is already active.</li><li>On Cinnamon/X11, be suspicious of solutions that depend on wtype or wl-copy.</li><li>Use F13 to F24 style macro keys when possible. They avoid shortcut conflicts.</li><li>Backup ~/.config/voxtype/config.toml before every experiment.</li><li>If paste mode starts printing numbers like 2442, suspect ydotool syntax compatibility.</li><li>If an upgrade makes transcription slow, check the GPU backend immediately.</li><li>If recognition is bad, test the actual microphone recording before changing five software settings.</li></ul><h3>Final Working Configuration</h3><p>This is the pragmatic configuration that worked for me on Linux Mint Cinnamon with Codex CLI:</p><pre>[hotkey]<br># &quot;X&quot; key from the four-symbol macro-key cluster on my keyboard<br>key = &quot;F16&quot;<br>mode = &quot;push_to_talk&quot;</pre><pre>[whisper]<br>model = &quot;base&quot;<br>language = &quot;it&quot;<br>translate = false</pre><pre>[output]<br># Paste mode was unreliable on this X11 setup; dotool typing is stable.<br>mode = &quot;type&quot;<br>fallback_to_clipboard = true<br>driver_order = [ &quot;dotool&quot; ]<br>type_delay_ms = 0</pre><pre>[output.notification]<br>on_recording_start = false<br>on_recording_stop = false<br>on_transcription = false</pre><h3>Conclusion</h3><p>The final solution is not the most elegant theoretical setup. It is the one that works.</p><p>I can now hold the F16 macro key, dictate a prompt, release it, and have the text appear inside Codex CLI. Everything runs locally. No cloud dictation. No browser extension. No extra GUI.</p><p>The remaining work is not Linux plumbing anymore. It is microphone quality and maybe a better Whisper model. That is a much better problem to have.</p><p><em>And now I can go back to my AI CLI terminal and start dictating my prompts… uhm…</em><strong><em> I am the “dictator” now! :-P</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7811482e7861" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I Built a Priority App on a Train]]></title>
            <link>https://medium.com/@antoniogallo.it/i-built-a-priority-app-on-a-train-0e2a6d714c75?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/0e2a6d714c75</guid>
            <category><![CDATA[vibe-coding]]></category>
            <category><![CDATA[time-management]]></category>
            <category><![CDATA[pomodoro-technique]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Fri, 29 May 2026 18:19:36 GMT</pubDate>
            <atom:updated>2026-05-29T18:19:36.484Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Urgenzo" src="https://cdn-images-1.medium.com/max/1024/1*m14VcUk9tIiW2hUi95jacw.png" /></figure><p>I was on a train, with about 90 minutes to spend.</p><p>I was reading a book called <em>How to Save an Hour a Day</em>.</p><p>At some point, the book started talking about the <em>Eisenhower Matrix</em>: <br>the classic way of organizing tasks based on importance and urgency.</p><p>In theory, nothing new.</p><p>I already knew the concept. <br>I use Todoist. <br>Over the years, I’ve tried more productivity tools than I can remember.</p><p>Task managers, kanban boards, calendar-based workflows, note-taking systems pretending to be task managers, and task managers pretending to be life operating systems.</p><p>But this time… <br><strong>one detail caught my attention</strong>.</p><p>The book suggested <strong>assigning a numeric value to both importance and urgency</strong>.</p><p>Not just putting a task into a quadrant.</p><p>A number.</p><p>That small difference changed the problem.</p><h3>The Problem With Most Todo Lists</h3><p>Most todo lists are very good at collecting things.</p><p>They are less good at helping you decide what deserves attention now.</p><p>You can add labels, projects, dates, colors, flags, sections, priorities, filters, and recurring rules. But in many tools, “priority” is still something you manually choose.</p><p>High. Medium. Low.</p><p>Or maybe P1, P2, P3, P4.</p><p>That works, but it always felt a bit too static to me.</p><p>Because a task is not urgent or important in isolation.</p><p>A task becomes more or less relevant depending on several factors:</p><ul><li>how important it is;</li><li>how urgent it is;</li><li>when it is due;</li><li>whether it has already been started;</li><li>how much attention it requires right now.</li></ul><p>The Eisenhower Matrix is useful because it forces you to separate importance from urgency.</p><p>But many digital tools flatten that distinction again.</p><p>They ask you to choose a priority, instead of helping you calculate one.</p><h3>The Missing Piece: Due Dates</h3><p>While thinking about importance and urgency, I realized there was another thing I never fully liked in task managers: how due dates are handled.</p><p>In many apps, a due date is mostly a label.</p><p>It tells you when something is due, but it does not always influence the actual order in which you should work.</p><p>Of course, you can sort by due date. But that creates another problem: a low-value task due tomorrow may appear more important than a high-value task that needs real focus.</p><p>So I started thinking about a third factor.</p><p>What if the due date was not just displayed, but actually contributed to the priority calculation?</p><p>That became the core idea:</p><pre>importance + urgency + due date = operational priority</pre><p>Not a perfect scientific model.</p><p>Just a practical one.</p><p>The kind of model that helps you answer a very simple question:</p><blockquote>What should I work on now?</blockquote><h3>Building the First MVP</h3><p>At that point, I was still on the train.</p><p>I had about 90 minutes.</p><p>The news about Claude Opus 5.8 had just come out, and I wanted to try it properly on a small but real project.</p><p>So I started building.</p><p>The first MVP ran inside Claude’s canvas. It was rough, but it already had the essential idea: create tasks, assign numeric values for importance and urgency, add due dates, and calculate a working priority.</p><p>It was not meant to be a polished product.</p><p>It was a way to test whether the idea made sense once it became interactive.</p><p>And it did.</p><p>When I got home, I continued with Codex and turned the prototype into a small installable PWA.</p><p>The funny part is that the slowest part of the process was not building the application.</p><p>It was waiting about 30 minutes for DNS propagation and configuring the Google OAuth keys.</p><h3>What Urgenzo Does</h3><p>The result is Urgenzo:</p><p><a href="https://urgenzo.com/">https://urgenzo.com</a></p><p>Urgenzo is a lightweight PWA for managing tasks by calculated priority.</p><p>The current version includes:</p><ul><li>task priority based on importance, urgency, and due date;</li><li>offline mode;</li><li>optional Google Drive sync;</li><li>automatic merge of local data after login;</li><li>Pomodoro timer;</li><li>multilingual interface;</li><li>installable PWA experience.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/371/1*DGZ35n0sVuqTIsa2QLyesA.png" /><figcaption>Pomodoro Timer included!</figcaption></figure><p>The <strong>Google Drive syn</strong>c was an important design choice.</p><p>I did not want to start with a central database, user accounts, billing, subscriptions, or yet another backend for a tiny personal productivity tool.</p><p>Instead, if users choose to sign in with Google, <strong>their data is saved in their own Google Drive account</strong>.</p><p>If they prefer not to sign in, they can use the app<strong> offline and keep everything on your device</strong>.</p><h3>Why Another Todo App?</h3><p>This is the obvious question.</p><p>The world does not need another generic todo app.</p><p>And I agree.</p><p>Urgenzo is not trying to replace Todoist, Notion, Things, Trello, Linear, or any other mature productivity system.</p><p>It started from a very specific irritation:</p><p>I wanted a task list where priority is not just selected manually, but calculated from separate values.</p><p>Importance should be one thing.</p><p>Urgency should be another.</p><p>Due date should matter too.</p><p>That small distinction was enough to justify building a tool around it.</p><p>Sometimes the best reason to build something is not that the market lacks tools.</p><p>It is that none of the tools you already use think exactly the way you want to think.</p><h3>What I Learned</h3><p>A few things stood out from this small experiment.</p><p>First, constraints help.</p><p>A train ride is a good product manager. You do not have infinite time, so you focus on the core mechanic.</p><p>Second, AI coding tools are extremely useful when the project is small, concrete, and easy to test manually.</p><p>The important part is not asking an AI to “build an app”.</p><p>The important part is having a clear behavior in mind and iterating aggressively.</p><p>Third, shipping still involves the boring parts.</p><p>DNS. OAuth setup. PWA updates. Icons. Copy. Edge cases. Buttons that look wrong. States that feel awkward.</p><p>These are not glamorous, but they are where a prototype becomes something usable.</p><h3>What’s Next</h3><p>Urgenzo is still early.</p><p>There are many things that could improve: better filtering, smarter priority tuning, recurring tasks, export options, keyboard shortcuts, and more refined mobile interactions.</p><p>But the first version is live, installable, and already useful.</p><p>That is enough for now.</p><p><strong>You can try it here:</strong></p><p><a href="https://urgenzo.com/"><strong>https://urgenzo.com</strong></a></p><p>Feedback, bug reports, and honest criticism are welcome.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0e2a6d714c75" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fix SSH Agent on Linux Mint 22 / Cinnamon: Solving the Logout Bug]]></title>
            <link>https://medium.com/@antoniogallo.it/fix-ssh-agent-on-linux-mint-22-cinnamon-solving-the-logout-bug-ba81face830b?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/ba81face830b</guid>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Thu, 09 Oct 2025 10:54:08 GMT</pubDate>
            <atom:updated>2025-10-09T10:54:08.879Z</atom:updated>
            <content:encoded><![CDATA[<p><em>How to definitively solve the SSH agent issue after logout/login on Linux Mint 22 with Cinnamon</em></p><h3>The Problem</h3><p>If you’ve upgraded to Linux Mint 22 with the Cinnamon desktop environment, you might have encountered an annoying bug: after the first logout and subsequent login, the SSH agent stops working. The SSH_AUTH_SOCK environment variable is no longer set, making SSH key authentication impossible.</p><p>Symptoms include:</p><ul><li>Slow terminal startup (tens of seconds)</li><li>$SSH_AUTH_SOCK empty or unset</li><li>systemd sessions stuck in “closing” state</li><li>gnome-keyring and ssh-agent processes lingering from previous sessions</li></ul><h3>The Root Cause</h3><p>The issue is caused by a regression in Linux Mint 22 where systemd --user processes and related services (like gnome-keyring-daemon) are not properly terminated on logout. These processes remain active between sessions, preventing the new session from properly initializing the SSH agent.</p><blockquote>Note: This is a confirmed bug discussed in the Linux Mint community. You can find the reference thread on <a href="https://github.com/linuxmint/linuxmint/issues/705">GitHub</a>.</blockquote><h3>The Solution</h3><p>The solution leverages LightDM’s native session-cleanup-script mechanism, which automatically executes a cleanup script at every graphical logout.</p><h3>Step 1: Create the Cleanup Script</h3><p>Create the file /usr/local/sbin/cinnamon-session-cleanup with the following content:</p><pre>#!/bin/bash<br># Script to clean up lingering systemd user processes on logout<br># Fixes SSH agent issues in Linux Mint 22<br><br># Log file for debugging (overwritten each logout)<br>LOG_FILE=&quot;/tmp/cinnamon-cleanup-$USER.log&quot;<br><br># Overwrite log file<br>echo &quot;$(date): Starting Cinnamon session cleanup for user: $USER&quot; &gt; &quot;$LOG_FILE&quot;<br><br># Get user ID from the username<br>if [ -z &quot;$USER&quot; ]; then<br>    echo &quot;$(date): ERROR: USER variable is not set&quot; &gt;&gt; &quot;$LOG_FILE&quot;<br>    exit 1<br>fi<br>USER_ID=$(id -u $USER 2&gt;/dev/null)<br>if [ -z &quot;$USER_ID&quot; ] || [ &quot;$USER_ID&quot; -eq 0 ]; then<br>    echo &quot;$(date): ERROR: Invalid or root USER_ID ($USER_ID) for user $USER&quot; &gt;&gt; &quot;$LOG_FILE&quot;<br>    exit 1<br>fi<br>echo &quot;$(date): Cleaning up processes for user $USER (UID: $USER_ID)&quot; &gt;&gt; &quot;$LOG_FILE&quot;<br><br># Kill lingering systemd --user process<br>SYSTEMD_PID=$(pgrep -u $USER_ID &quot;systemd&quot; | head -1)<br>if [ -n &quot;$SYSTEMD_PID&quot; ]; then<br>    echo &quot;$(date): Killing systemd --user process (PID: $SYSTEMD_PID)&quot; &gt;&gt; &quot;$LOG_FILE&quot;<br>    kill -TERM &quot;$SYSTEMD_PID&quot; 2&gt;&gt; &quot;$LOG_FILE&quot;<br>    sleep 1<br><br>    # Force kill if still running<br>    if kill -0 &quot;$SYSTEMD_PID&quot; 2&gt;/dev/null; then<br>        echo &quot;$(date): Force killing systemd --user process&quot; &gt;&gt; &quot;$LOG_FILE&quot;<br>        kill -KILL &quot;$SYSTEMD_PID&quot; 2&gt;&gt; &quot;$LOG_FILE&quot;<br>    fi<br>fi<br><br># Clean up any lingering ssh-agent processes<br>SSH_AGENT_PIDS=$(pgrep -u $USER_ID &quot;ssh-agent&quot;)<br>if [ -n &quot;$SSH_AGENT_PIDS&quot; ]; then<br>    echo &quot;$(date): Killing ssh-agent processes: $SSH_AGENT_PIDS&quot; &gt;&gt; &quot;$LOG_FILE&quot;<br>    kill -TERM $SSH_AGENT_PIDS 2&gt;&gt; &quot;$LOG_FILE&quot;<br>fi<br><br># Clean up gnome-keyring-daemon<br>KEYRING_PIDS=$(pgrep -u $USER_ID &quot;gnome-keyring&quot;)<br>if [ -n &quot;$KEYRING_PIDS&quot; ]; then<br>    echo &quot;$(date): Killing gnome-keyring processes: $KEYRING_PIDS&quot; &gt;&gt; &quot;$LOG_FILE&quot;<br>    kill -TERM $KEYRING_PIDS 2&gt;&gt; &quot;$LOG_FILE&quot;<br>fi<br>echo &quot;$(date): Cleanup completed&quot; &gt;&gt; &quot;$LOG_FILE&quot;</pre><p>Set the correct permissions:</p><pre>sudo chown root:root /usr/local/sbin/cinnamon-session-cleanup<br>sudo chmod 755 /usr/local/sbin/cinnamon-session-cleanup</pre><h3>Step 2: Configure LightDM</h3><p>Create the configuration file /etc/lightdm/lightdm.conf.d/99-cleanup.conf:</p><pre>[Seat:*]<br>session-cleanup-script=/usr/local/sbin/cinnamon-session-cleanup</pre><p><strong>Important</strong>: After creating the LightDM configuration, you need to restart LightDM for the changes to take effect. You can either:</p><p>Option 1 — Restart LightDM (recommended):</p><pre>sudo systemctl restart lightdm</pre><p>Warning: This will immediately terminate your current graphical session. Save all your work first!</p><p>Option 2 — Reboot the system.</p><p>Without restarting LightDM or rebooting, the cleanup script won’t be executed on logout.</p><h3>Step 3: Test the Solution</h3><p>After restarting LightDM or rebooting, logout and log back in.</p><p>After login, verify everything works:</p><pre># Check if SSH_AUTH_SOCK is set<br>echo $SSH_AUTH_SOCK<br><br># Verify the socket exists<br>ls -la $SSH_AUTH_SOCK<br><br># Check the cleanup script log<br>cat /tmp/cinnamon-cleanup-$USER.log</pre><p><strong>Success:</strong> If $SSH_AUTH_SOCK is set and the socket exists, the problem is solved. The log will show which processes were terminated during the previous logout.</p><p><strong>Troubleshooting</strong>: If the log file /tmp/cinnamon-cleanup-$USER.log doesn&#39;t exist after logout, it means the cleanup script was not executed. This usually indicates that LightDM wasn&#39;t restarted after creating the configuration file.</p><h3>Why This Solution Works</h3><p>The solution leverages LightDM’s native session-cleanup-script mechanism, which:</p><ul><li>Executes automatically at every graphical logout</li><li>Runs with root privileges (necessary to terminate systemd processes)</li><li>Receives the $USER variable set to the user logging out</li><li>Is a stable and documented feature of LightDM</li></ul><p>The script cleanly terminates:</p><ol><li>Lingering systemd --user processes</li><li>Orphaned ssh-agent processes</li><li>Stuck gnome-keyring daemons</li></ol><p>This allows the new session to properly initialize all services, including the SSH agent.</p><h3>Non-Working Alternatives</h3><p>During the analysis of this problem, several alternatives were considered that do not fully solve the issue:</p><ul><li>Script in ~/.bash_logout: Only works for terminal logout, not graphical logout</li><li>Wrapper for cinnamon-session-quit: Complex and fragile to updates</li><li>Direct patching of system scripts: Lost with every package update</li><li>Workaround in ~/.bashrc: Doesn&#39;t fix the cause, only the symptoms</li></ul><h3>Advantages of This Solution</h3><ul><li>✅ Native: Uses standard LightDM mechanisms</li><li>✅ Reliable: Guaranteed to execute on every logout</li><li>✅ Persistent: Survives system updates</li><li>✅ Clean: Doesn’t modify system files</li><li>✅ Debuggable: Complete log in /tmp/cinnamon-cleanup-$USER.log</li></ul><h3>Conclusions</h3><p>The SSH agent bug in Linux Mint 22 is caused by systemd processes that aren’t properly terminated on logout. The solution using LightDM’s session-cleanup-script definitively solves the problem in a clean and elegant way, without workarounds or fragile patches.</p><p>This solution has been tested on Linux Mint 22 with Cinnamon 6.2.7 and works perfectly. If you’ve encountered the same problem, try this solution and let me know how it goes!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ba81face830b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I Built a Production-Ready PWA in 24 Hours Without Writing a Single Line of Code (Using Claude…]]></title>
            <link>https://medium.com/@antoniogallo.it/how-i-built-a-production-ready-pwa-in-24-hours-without-writing-a-single-line-of-code-using-claude-f283dc09b87d?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/f283dc09b87d</guid>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[pwa]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Wed, 23 Jul 2025 11:30:19 GMT</pubDate>
            <atom:updated>2025-07-23T11:30:19.519Z</atom:updated>
            <content:encoded><![CDATA[<h3>How I Built a Production-Ready PWA in 24 Hours Without Writing a Single Line of Code (Using Claude Code)</h3><p><em>Or: The Day I Became a “Developer” by Being a Good Product Manager</em></p><h3>TL;DR</h3><p>I built a fully functional Progressive Web App for travel planning in less than 24 hours without writing any code myself. The secret? <strong>No build process. No backend. No complexity.</strong> Just static files and local storage.</p><p>Instead of wasting time on infrastructure, I acted as a product manager while Claude Code handled implementation. The result? A production-ready app with offline support, Google Places integration, route optimization, and mobile-first design. <strong>Ship fast, iterate later.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/579/1*EWXyQ21fZPPQ9p61bNiPBg.png" /></figure><h3>The Challenge: “I Need a Travel Planner App… Yesterday”</h3><p>Like many of you, I’ve been frustrated with existing travel planning apps. They’re either <strong>too complex</strong>, require <strong>expensive subscriptions</strong>, or <strong>don’t work offline</strong> when you actually need them while traveling. I wanted something simple:</p><ul><li><strong>Add points of interest (POIs)</strong> to my trip</li><li><strong>See them on a map</strong> with real-time visualization</li><li><strong>Calculate optimal routes</strong> between locations</li><li><strong>Work 100% offline</strong> after first load</li><li><strong>No accounts, no cloud</strong>, just local storage</li></ul><p>The problem? I’m not a developer. I understand technology, I can read code, but asking me to set up a <strong>modern JavaScript development environment</strong>? That’s where I usually give up.</p><h3>Enter Claude Code: My AI Pair Programmer (Who Does All the Programming)</h3><p>I’d heard about <a href="https://claude.ai/code"><strong>Claude Code</strong></a>, Anthropic’s coding assistant, and decided to give it a shot. My approach was different from typical tutorials — <strong>I wouldn’t write any code</strong>. Instead, I would:</p><ol><li><strong>Be the Product Manager</strong>: Define features clearly</li><li><strong>Be the QA Tester</strong>: Test everything thoroughly</li><li><strong>Be the Designer</strong>: Provide UI/UX feedback</li><li><strong>Be the User</strong>: Use the app and report issues</li></ol><p>Claude Code would be my <strong>entire development team</strong>.</p><h3>The Secret Weapon: CLAUDE.md — My Development Contract</h3><p>Before writing any code, I did something that <strong>changed everything</strong>: I created a detailed specification document called CLAUDE.md. This wasn&#39;t just documentation - it was a <strong>contract between me and my AI developer</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LHXOTqxQpfdNmsPUyRe0cg.png" /></figure><p>The document included:</p><ul><li><strong>Project Overview</strong>: Clear description of what RouteMyWay should do</li><li><strong>Core Features</strong>: Detailed list of functionalities</li><li><strong>Tech Stack</strong>: My chosen architecture (<a href="https://preactjs.com/">Preact</a> + <a href="https://github.com/developit/htm">HTM</a> + <a href="https://preactjs.com/guide/v10/signals/">Signals</a>)</li><li><strong>Project Structure</strong>: Exact file organization</li><li><strong>Implementation Examples</strong>: Code snippets showing the preferred style</li><li><strong>Phase-by-phase Task List</strong>: Breaking down the work into testable chunks</li></ul><p>The game-changer was the task list at the end:</p><pre>## Phase 1: Core Foundation ✅<br>- [ ] Create project structure<br>- [ ] Setup basic HTML with CDN links<br>- [ ] CSS styles<br>- [ ] Create basic app.js with Preact initialization<br>- [ ] Test that app loads without errors</pre><pre>## Phase 2: State Management 📊<br>- [ ] Implement store.js with signals<br>[... and so on for 13 phases]</pre><p><strong>My instruction to Claude Code</strong>: “Stop at the end of each phase so I can test the UI”</p><p>This approach meant:</p><ol><li>I always knew what was being built</li><li>I could test incrementally (no big bang integration)</li><li>Claude Code had clear context for every decision</li><li>We caught issues early, not after 1000 lines of code</li></ol><h3>The Productivity Hack: SHIFT+TAB for Warp Speed Development</h3><p>Here’s a Claude Code power user tip that cut development time in half:</p><p><strong>SHIFT+TAB</strong> toggles between two modes:</p><ul><li><strong>Plan Mode</strong>: Claude explains what it will do before doing it</li><li><strong>Auto-Accept Mode</strong>: Claude makes changes immediately without confirmation</li></ul><p>It’s almost mandatory to use SHIFT+TAB to make this work efficiently. My workflow:</p><ol><li><strong>Start in Plan Mode</strong> for architectural decisions</li><li><strong>Switch to Auto-Accept</strong> (SHIFT+TAB) once we agree on approach</li><li><strong>Let Claude Code fly</strong> — implementing exactly what we discussed at maximum speed</li><li><strong>No reviewing every single edit</strong> — I trust the plan we made</li><li><strong>Test the result</strong> at phase completion</li></ol><p>This is crucial because reviewing 50 individual file edits is exhausting and kills momentum. By agreeing on the plan upfront and then switching to auto-accept, I maintained flow state while Claude Code executed at full speed. Without auto-accept mode, you’ll spend more time reviewing code than actually building — defeating the whole purpose of AI-assisted development.</p><p><strong>Important</strong>: This only works because of the CLAUDE.md specification and phased approach. You need clear guardrails before unleashing auto-accept mode. But once you have them, SHIFT+TAB is your best friend for rapid development.</p><h3>The Architecture Discussion That Set Everything Up</h3><p>With CLAUDE.md as our guide, the first conversation was smooth. I came prepared with a specific architecture in mind:</p><p><strong>Me</strong>: “I want a travel planner PWA with Preact + HTM + Signals. No build process, just static files I can serve anywhere. Use Leaflet for maps, LocalForage for storage.”</p><p><strong>Claude Code</strong>: <em>Acknowledges and implements the architecture exactly as specified</em></p><p>This was my key insight — I knew <strong>modern tools that work without build processes</strong>:</p><ul><li><a href="https://preactjs.com/"><strong>Preact</strong></a><strong> + </strong><a href="https://github.com/developit/htm"><strong>HTM</strong></a>: React-like development without JSX compilation</li><li><a href="https://preactjs.com/guide/v10/signals/"><strong>Signals</strong></a>: Simple state management without Redux complexity</li><li><strong>CDN loading</strong>: No npm, no node_modules, no webpack</li><li><strong>Static files</strong>: Deploy anywhere, edit with any text editor</li></ul><p>But the most crucial decision? <strong>No backend</strong>. This wasn’t about being fancy — it was about speed:</p><p><strong>The 24-Hour Magic Formula:</strong></p><ul><li><strong>No build process</strong> = Start coding immediately (save 2–4 hours)</li><li><strong>No backend API</strong> = Skip authentication, databases, deployments (save 8–12 hours)</li><li><strong>No npm/webpack</strong> = No dependency hell or build configs (save 2–3 hours)</li><li><strong>CDN libraries</strong> = Just add script tags and go (save 1–2 hours)</li><li><strong>Local storage only</strong> = No server state to manage (save 4–6 hours)</li></ul><p>Total time saved: <strong>17–27 hours</strong> of typical development overhead!</p><p><strong>What we built instead:</strong></p><ul><li><a href="https://localforage.github.io/localForage/"><strong>LocalForage</strong></a>: IndexedDB storage that works offline</li><li><strong>No user accounts</strong>: No authentication complexity</li><li><strong>No API to build</strong>: Just integrate existing services (<a href="https://www.openstreetmap.org/">OpenStreetMap</a>, <a href="https://developers.google.com/maps/documentation/places/web-service">Google Places</a>)</li><li><strong>Simple hosting</strong>: Just rsync to any static server</li><li><strong>No database</strong>: Your data stays on your device</li></ul><p>This “local-first” approach meant we could focus 100% on user experience instead of wrestling with servers, databases, and deployment pipelines. <strong>We went from idea to working app in 24 hours because we didn’t build what we didn’t need.</strong></p><p>The philosophy is simple: <strong>Get a working prototype FAST, then add features later.</strong> Need user accounts? Add them in v2. Want cloud sync? Ship it next week. But on day one? Ship something that actually works!</p><p>Claude Code perfectly implemented my architectural vision. The lesson? Coming with a clear technical direction accelerates everything.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/582/1*bd0HDfB1nJ5QlGoB8G6qGw.png" /></figure><h3>The Development Process: A Real Conversation</h3><p>Here’s what our actual workflow looked like:</p><h3>Hour 1–4: Core Structure</h3><p><strong>Me</strong>: “Create the basic app structure with trips and days”<br><strong>Claude Code</strong>: <em>Creates file structure, implements basic components</em><br><strong>Me</strong>: “The trip cards need edit and delete buttons”<br><strong>Claude Code</strong>: <em>Adds the features</em><br><strong>Me</strong>: “Actually, I want inline editing for trip names”<br><strong>Claude Code</strong>: <em>Refactors to support inline editing</em></p><h3>Hour 5–8: The Map Integration</h3><p><strong>Me</strong>: “Add a map view with POI markers”<br><strong>Claude Code</strong>: <em>Integrates </em><a href="https://leafletjs.com/"><em>Leaflet</em></a><em>, creates map component</em><br><strong>Me</strong>: “The markers are too small on mobile”<br><strong>Claude Code</strong>: <em>Adjusts sizes and adds touch-friendly interactions</em></p><h3>Hour 9–12: Search Functionality</h3><p><strong>Me</strong>: “I need to search for POIs”<br><strong>Claude Code</strong>: <em>Implements </em><a href="https://nominatim.org/"><em>OpenStreetMap Nominatim</em></a><em> search</em><br><strong>Me</strong>: “Can we add Google Places for better results?”<br><strong>Claude Code</strong>: <em>Adds optional </em><a href="https://developers.google.com/maps/documentation/places/web-service"><em>Google Places API</em></a><em> integration</em><br><strong>Me</strong>: “It’s making too many API requests!”<br><strong>Claude Code</strong>: <em>Implements grid-based caching system</em></p><h3>Hour 13–16: Route Optimization</h3><p><strong>Me</strong>: “Add route calculation between POIs”<br><strong>Claude Code</strong>: <em>Implements nearest-neighbor algorithm</em><br><strong>Me</strong>: “It should return to base at the end”<br><strong>Claude Code</strong>: <em>Adds circular route logic</em></p><h3>Hour 17–20: Mobile UI Fixes</h3><p>This is where it got interesting. <strong>Real users (me) testing on real devices</strong>:</p><p><strong>Me</strong>: “The FAB button is hidden behind Android’s navigation bar”<br><strong>Claude Code</strong>: <em>Adds safe area insets and dynamic viewport heights</em></p><p><strong>Me</strong>: “Buttons are too small for fingers on mobile”<br><strong>Claude Code</strong>: <em>Implements </em><strong><em>44px touch targets</em></strong><em> per </em><a href="https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/"><em>iOS Human Interface Guidelines</em></a></p><p><strong>Me</strong>: “The daily view UI is terrible on mobile!”<br><strong>Claude Code</strong>: <em>Creates responsive card layout for small screens</em></p><h3>Hour 21–24: Polish and PWA Features</h3><p><strong>Me</strong>: “Add offline support and make it installable”<br><strong>Claude Code</strong>: <em>Implements service worker with </em><a href="https://developers.google.com/web/tools/workbox"><em>Workbox</em></a><br><strong>Me</strong>: “Version updates aren’t working properly”<br><strong>Claude Code</strong>: <em>Adds version management and update notifications</em></p><h3>The Deployment Magic: From Localhost to Production in Minutes</h3><p>Here’s where the no-build architecture really shined. My deployment process:</p><p><strong>Local Development</strong>: Simple Python HTTP server</p><pre>python3 -m http.server 8000</pre><p>That’s it. No npm install, no build watch, no hot reload complexity.</p><p><strong>Version Management</strong>: The PWA Update Magic</p><p>Here’s the clever part — PWAs cache everything for offline use, which creates a problem: how do users know there’s a new version? My solution connects version management to PWA updates:</p><pre>// Auto-generated version file - DO NOT EDIT MANUALLY<br>const APP_VERSION = &#39;1.0.10&#39;;<br>const BUILD_TIMESTAMP = &#39;2025-07-22T21:12:04Z&#39;;</pre><p>This simple file solves multiple problems:</p><p><strong>For PWA Installation:</strong></p><ul><li>Service worker includes version.js in its cache manifest</li><li>Each deployment changes the version number</li><li>Changed version = changed file hash = service worker update trigger</li><li>Users who installed the PWA get automatic update notifications</li></ul><p><strong>How it works:</strong></p><ol><li>User installs PWA (it caches version.js with “1.0.23”)</li><li>I deploy new version (Makefile bumps to “1.0.24”)</li><li>User opens PWA next time</li><li>Service worker sees version.js changed</li><li>Shows “Update available!” notification</li><li>User clicks update, gets latest version</li><li>Without this trick, PWA users would be stuck on old versions forever since everything is cached offline!</li></ol><p><strong>1My Makefile</strong>: Dead simple deployment</p><pre># Route My Way - Simple deployment<br><br>VERSION_FILE = public/js/version.js<br>CURRENT_VERSION := $(shell grep &quot;APP_VERSION&quot; $(VERSION_FILE) 2&gt;/dev/null | cut -d&quot;&#39;&quot; -f2 || echo &quot;1.0.0&quot;)<br><br># Increment version and deploy<br>publish:<br> $(eval NEW_VERSION := $(shell echo $(CURRENT_VERSION) | awk -F. &#39;{print $$1&quot;.&quot;$$2&quot;.&quot;$$3+1}&#39;))<br> @echo &quot;// Auto-generated version file - DO NOT EDIT MANUALLY&quot; &gt; $(VERSION_FILE)<br> @echo &quot;const APP_VERSION = &#39;$(NEW_VERSION)&#39;;&quot; &gt;&gt; $(VERSION_FILE)<br> @echo &quot;const BUILD_TIMESTAMP = &#39;$$(date -u +%Y-%m-%dT%H:%M:%SZ)&#39;;&quot; &gt;&gt; $(VERSION_FILE)<br> @echo &quot;Version: $(CURRENT_VERSION) → $(NEW_VERSION)&quot;<br> <br> rsync -avz --delete --progress public/ www.routemyway.com:/var/www/public/</pre><p>One command updates version, deploys files, and triggers PWA updates for all users!</p><p><strong>The Service Worker Integration:</strong></p><pre>// Import version from auto-generated file<br>importScripts(&#39;/js/version.js&#39;);</pre><p>This is crucial for PWAs because:</p><ul><li>Regular websites: Users refresh and get new version automatically</li><li>PWAs: Everything cached offline, users might never get updates</li><li>This trick: Automatic update notifications for installed PWAs</li></ul><p><strong>Production</strong>: Static Nginx hosting</p><ul><li>No Node.js server</li><li>No PM2 process manager</li><li>No Docker containers</li><li>Just static files served fast</li></ul><p>The result? Deploy in literally 5 seconds:</p><pre>make publish</pre><p>From localhost:8000 to <a href="https://www.routemyway.com/">https://www.routemyway.com/</a> in one command.</p><p>No CI/CD pipeline, no build artifacts, no deployment slots. Just files copying to a server.</p><h3>The Surprising Parts</h3><h3>1. Debugging Without Seeing</h3><p>Claude Code can’t see the screen or run the code. Every bug had to be described:</p><ul><li>“The tourist information centers aren’t showing”</li><li>“Console shows ‘setAsBase is not defined’ error”</li><li>“The cache seems ignored when I move 50 meters”</li></ul><p>Yet it successfully debugged complex issues through description alone.</p><h3>2. Handling My Frustration</h3><p>Yes, I got frustrated. When the Google Places API wasn’t working after multiple attempts, I actually wrote: “please update to the f***ing beta version of the new api”</p><p>Then immediately followed with: “yeah I’m sorry Claude you make me nervous sometimes”</p><p>Claude Code calmly fixed the issue without taking offense. Try that with a human developer! The ability to vent frustration and then apologize to an AI that just keeps helping? Priceless.</p><h3>3. Architecture Decisions That Scaled</h3><p>The initial architecture decisions (no build process, CDN-based libraries, signal-based state) held up throughout development. We never had to refactor the fundamental structure.</p><h3>The Technical Achievements</h3><p>Without writing a single line of code myself, we built:</p><ol><li><strong>Complete PWA Features:</strong></li></ol><ul><li><strong>Offline support</strong> with intelligent caching</li><li><strong>Install prompts</strong> for home screen</li><li><strong>Auto-update detection</strong> via version.js</li><li><strong>iOS and Android optimizations</strong></li></ul><p><strong>2. Advanced Functionality:</strong></p><ul><li><strong>Dual search APIs</strong> (OSM + Google Places)</li><li><strong>Grid-based POI caching</strong> to minimize API calls</li><li><strong>Route optimization</strong> with return-to-base</li><li><strong>Real-time location tracking</strong></li></ul><p><strong>3. Production-Ready UI:</strong></p><ul><li><strong>Responsive design</strong> that actually works</li><li><strong>44px touch targets</strong> for mobile</li><li><strong>Safe area handling</strong> for modern devices</li><li><strong>Proper mobile viewport</strong> management</li></ul><p>4. <strong>Developer-Friendly Architecture:</strong></p><ul><li><strong>No build process</strong> needed</li><li>All state in <strong>signals</strong> (reactive)</li><li><strong>Clean component structure</strong></li><li><strong>Proper error handling</strong></li><li><strong>Version auto-increment</strong> via Makefile</li><li><strong>5-second deployments</strong> with rsync</li><li><strong>Service worker updates</strong> on version change</li></ul><h3>The Mistakes We Made (And Fixed)</h3><ol><li><strong>Over-engineering Mobile UI</strong>: First attempt at mobile optimization made desktop worse</li><li><strong>Cache Key Design</strong>: Initially included zoom level, causing unnecessary API calls</li><li><strong>Button Placement</strong>: Moved buttons to the left on mobile, broke desktop layout</li><li><strong>API Integration</strong>: Tried using deprecated Google Places API three times</li></ol><p>Each mistake was caught by testing and fixed through conversation.</p><h3>Lessons Learned</h3><h3>1. Write a Spec First (CLAUDE.md is Your Best Friend)</h3><p>Before asking for any code, write a <strong>detailed specification</strong>. Include examples, architecture decisions, and a phased task list. This one document saved hours of back-and-forth.</p><h3>2. Demand Incremental Testing</h3><p>“Stop at each phase so I can test” — This single instruction prevented countless bugs. <strong>Never let an AI write 500 lines without testing</strong>.</p><h3>3. Be a Product Manager, Not a Coder</h3><p>The clearer my requirements, the better the output. <strong>Vague requests led to wrong implementations</strong>.</p><h3>4. Describe Problems Precisely</h3><p>“It doesn’t work” wastes time. <strong>“Console shows X error when I click Y”</strong> gets instant fixes.</p><h3>5. Trust Your Architecture</h3><p>When you choose an architecture (like my no-build approach), <strong>stick with it</strong>. It saved hours of setup time.</p><h3>6. Mobile-First Really Matters</h3><p>Most issues appeared on mobile. <strong>Should have tested mobile earlier</strong>.</p><h3>The Result</h3><p>In 24 hours, without writing code, I have:</p><ul><li>A <strong>fully functional PWA</strong> deployed at <a href="https://www.routemyway.com/">https://www.routemyway.com/</a></li><li><strong>100% Lighthouse PWA score</strong></li><li><strong>Works offline</strong> after first load</li><li><strong>Installs on iOS and Android</strong></li><li><strong>Actually useful</strong> for my next trip</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/588/1*6aPo6mfF-AEvr_XOEh-cGg.png" /></figure><h3>Could You Do This?</h3><p>Absolutely. You need:</p><ol><li><strong>Clear thinking</strong> about what you want</li><li><strong>Patience</strong> to test and describe issues</li><li><strong>Basic understanding</strong> of how web apps work</li><li><strong>Claude Code</strong> subscription</li><li><strong>SHIFT+TAB</strong> keyboard shortcut memorized (this is almost mandatory!)</li></ol><p>You don’t need:</p><ul><li>Programming experience</li><li>Development environment</li><li>Build tools knowledge</li><li>Years of training</li></ul><p>Pro tip: The difference between a 24-hour project and a 72-hour project? Using auto-accept mode once you trust your plan. Seriously, SHIFT+TAB is almost mandatory — without it, you’ll spend 80% of your time clicking “Accept” buttons instead of building. Plan first, then let Claude Code execute at full speed.</p><h3>The Future of Development?</h3><p>This experience made me reconsider what “development” means. If I can build a production app by describing what I want, what does that mean for:</p><ul><li><strong>Experienced developers</strong>: Focus on architecture and complex problems</li><li><strong>Product managers</strong>: Directly build prototypes and MVPs</li><li><strong>Entrepreneurs</strong>: Test ideas without technical co-founders</li><li><strong>Education</strong>: Teach problem-solving, not syntax</li></ul><h3>Try It Yourself</h3><p>The app is live at <a href="https://www.routemyway.com/">https://www.routemyway.com/</a> — try it out!</p><p>Want to build something similar? Here’s my advice:</p><ol><li><strong>Start with a clear problem</strong> you personally face</li><li><strong>Write a detailed CLAUDE.md</strong> specification first</li><li><strong>Choose “boring” architecture</strong> (no backend = no complexity)</li><li><strong>Demand phased development</strong> with testing breaks</li><li><strong>Be specific</strong> about requirements</li><li><strong>Test everything</strong> on real devices</li><li><strong>Don’t be afraid</strong> to say “this sucks, fix it”</li></ol><p>Remember: <strong>Every feature you don’t build is a feature that can’t break</strong>. My app has no servers to crash, no database to corrupt, no API keys to leak. It just works.</p><p><strong>Useful Resources:</strong></p><ul><li><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code Documentation</a></li><li><a href="https://preactjs.com/guide/v10/getting-started">Preact Getting Started</a></li><li><a href="https://web.dev/progressive-web-apps/">Progressive Web Apps Guide</a></li><li><a href="https://localforage.github.io/localForage/">LocalForage Documentation</a></li></ul><h3>Conclusion</h3><p>I built a real app in 24 hours without coding. Not a prototype, not a demo — a production app I’m using for my trips.</p><p><strong>The secret wasn’t AI magic. It was radical simplicity:</strong></p><ul><li>No build process = No setup time</li><li>No backend = No server complexity</li><li>No database = No schema design</li><li>Just HTML, CSS, and JavaScript from CDNs</li></ul><p><strong>This infrastructure choice saved 20+ hours of typical setup time.</strong> That’s why we could build a complete app in 24 hours.</p><p>The barrier to building software has fundamentally changed. The question isn’t “Can I build this?” anymore. It’s “What should I build?” and more importantly: <strong>“What can I ship TODAY?”</strong></p><p>Remember: You can always add complexity later. But you can’t ship later if you’re still configuring webpack.</p><p>What will you build in your next 24 hours?</p><p><em>Follow me for more no-code development adventures and buildless PWA projects!</em></p><p>See also: <a href="https://medium.com/@antoniogallo.it/creating-a-buildless-javascript-application-with-preact-htm-and-signal-f99386ad36d4">https://medium.com/@antoniogallo.it/creating-a-buildless-javascript-application-with-preact-htm-and-signal-f99386ad36d4</a></p><p>You can find me at: <a href="https://www.antoniogallo.it/">https://www.antoniogallo.it/</a></p><h3>Appendix: Actual Conversation Snippets</h3><p>For the skeptics, here are real exchanges from our development session:</p><p><strong>On caching issues:</strong></p><blockquote><em>Me: “cached poi seems ignored when i just move by a miserable 50 meter on the map”<br>Claude Code: [Implements 1.1km grid cells for consistent caching]</em></blockquote><p><strong>On mobile UI:</strong></p><blockquote><em>Me: “the thumb is 60x60 can it be large on card?”<br>Claude Code: [Updates to 120px images on mobile cards]</em></blockquote><p><strong>On architecture:</strong></p><blockquote><em>Me: “No build process required! Just serve static files”<br>Claude Code: [Designs entire app around CDN-loaded modules]</em></blockquote><p>This wasn’t magic. It was clear communication plus capable AI.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f283dc09b87d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why I Created a Custom Router for Preact]]></title>
            <link>https://medium.com/@antoniogallo.it/why-i-created-a-custom-router-for-preact-911cdf82d138?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/911cdf82d138</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[router]]></category>
            <category><![CDATA[preact]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Sun, 23 Jun 2024 17:56:11 GMT</pubDate>
            <atom:updated>2024-06-24T21:38:34.180Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Fngqe3VXcm25wIx-lqAtww.jpeg" /></figure><p>The<strong> deprecation of </strong><strong>preact-router</strong> left a gap for those of us who rely on Preact for building lightweight and efficient web applications.</p><p>While preact-iso has emerged as a powerful alternative, it unfortunately <strong>does not support hash-based navigation</strong>.</p><p>This posed a significant challenge for projects that rely on hash navigation, such as hybrid apps using WebViews in frameworks like Ionic and Cordova.</p><p>Additionally, while there are other routing solutions available, such as wouter and react-router, these options often come with a level of complexity and size that I wanted to avoid.</p><p>My goal was to create something <strong>minimalistic and specifically tailored to Preact</strong>, making use of preact-signals for reactive state management and htm for a JSX-like syntax without the overhead.</p><p>Thus, I set out to build a custom router that meets these specific needs:</p><ol><li><strong>Compatibility with Hash and URL Navigation</strong>: Ensuring <strong>seamless support for both hash-based navigation and standard URL</strong> navigation to cater to a wide range of web applications and hybrid apps.</li><li><strong>Lightweight and Minimal</strong>: Avoiding the bloat and complexity of larger routing libraries, <strong>providing a lean solution (<em>66 lines of code</em>) </strong>for developers who value simplicity and performance.</li><li><strong>Designed for Preact</strong>: Specifically tailored to work smoothly with Preact, leveraging preact-signals for an optimal development experience.</li></ol><p>This custom router addresses the unique requirements of developers who need a simple yet powerful routing solution for Preact applications, both in the browser and in hybrid app environments.</p><p>Example using HTM in place of JSX:</p><pre>&lt;${Router} prefix=&quot;/#&quot; fallback=${PageNotFound}&gt;<br>    &lt;${Route} path=&quot;/&quot; component=${PageLogin}/&gt;<br>    &lt;${Router} path=&quot;/uno/*&quot; prefix=&quot;/#/uno&quot; fallback=${PageNotFound}&gt;<br>        &lt;${Route} path=&quot;/uno/&quot; component=${PageUno}/&gt;<br>        &lt;${Route} path=&quot;/due/*&quot; component=${PageDue}/&gt;<br>        &lt;${Route} path=&quot;*&quot; component=${PageNotFound}/&gt;<br>    &lt;//&gt;<br>    &lt;${Route} path=&quot;/due/*&quot; component=${PageCorsi}/&gt;<br>&lt;//&gt;</pre><p>And you can navigate in those ways:</p><pre>&lt;a class=&quot;btn&quot; href=&quot;#/due/alfa/&quot;&gt;TEST2&lt;/a&gt;<br>&lt;button class=&quot;btn&quot; onclick=&quot;${()=&gt;{route(&#39;#/due/alfa/&#39;)}}&quot;&gt;TEST2&lt;/button&gt;</pre><p>You can grab the library from github:</p><p><a href="https://github.com/badpenguin/preact-signal-browser-router">GitHub - badpenguin/preact-signal-browser-router: A barebone hash and url router for browsers using preact+signals</a></p><h3>About Me</h3><p>I have got 40+ years experience in Open Source technologies. I am a Developer, Speaker and Trainer. Please check my <a href="https://www.linkedin.com/in/galloantonio/">Linkedin profile</a> or <a href="https://antoniogallo.it/">https://antoniogallo.it</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=911cdf82d138" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating a Buildless JavaScript Application with Preact, HTM, and Signal]]></title>
            <link>https://medium.com/@antoniogallo.it/creating-a-buildless-javascript-application-with-preact-htm-and-signal-f99386ad36d4?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/f99386ad36d4</guid>
            <category><![CDATA[jsx]]></category>
            <category><![CDATA[pwa]]></category>
            <category><![CDATA[preact]]></category>
            <category><![CDATA[webpack]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Wed, 19 Jun 2024 10:36:13 GMT</pubDate>
            <atom:updated>2024-06-19T10:36:13.142Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YFcdBA9Csyvhed1hv5y6Mg.jpeg" /></figure><p>In the era of modern web development, the <strong>complexity of build tools</strong> can sometimes become a barrier for beginners and those who <strong>prefer to keep things simple</strong> (K.I.S.S.).</p><p>However, with the emergence of libraries like Preact, HTM, and Signal, <strong>creating a buildless JavaScript application</strong> has become not only feasible but also efficient.</p><p>This approach allows developers to <strong>set up a project quickly without the overhead</strong> of build configurations, making the development process faster and more straightforward.</p><h4>Why Choose Preact, HTM, and Signal?</h4><ul><li><a href="https://preactjs.com/guide/v10/getting-started/#no-build-tools-route"><strong>Preact</strong></a>: A fast 3kB alternative to React with the same modern API, Preact provides the thinnest possible Virtual DOM abstraction on top of the DOM. Its simplicity and small size make it ideal for buildless setups where every byte counts.</li><li><a href="https://github.com/developit/htm"><strong>HTM</strong></a>: Hyperscript Tagged Markup (HTM) allows you to use JSX-like syntax directly in the browser without transpilation. HTM pairs beautifully with Preact to offer a developer-friendly template making experience without a build step.</li><li><a href="https://preactjs.com/guide/v10/signals/"><strong>Signals</strong></a>: A state management library that works seamlessly with Preact, offering a simple and efficient way to handle application state with fine-grained reactive programming.</li></ul><h4>Setting Up a Buildless Environment</h4><p>You can <strong>start from my skeleton</strong> App from github here:</p><p><a href="https://github.com/badpenguin/buildless-pwa"><strong>https://github.com/badpenguin/buildless-pwa</strong></a></p><p>The whole buildless concept<strong> is based on 2 pillars</strong>:</p><ol><li>ES Modules and <strong>ImportMap — in place of a bundler;</strong></li><li>A template literals library, <strong>HTM — in place of JSX.</strong></li></ol><p>So your browser must be compatibles with ImportMap, this is not always the case. That’s why <strong>I suggest to add this polyfill</strong> at the beginning:</p><p><a href="https://github.com/guybedford/es-module-shims">https://github.com/guybedford/es-module-shims</a></p><p>i.e.:</p><pre>&lt;script async src=&quot;https://ga.jspm.io/npm:es-module-shims@1.7.3/dist/es-module-shims.js&quot;&gt;&lt;/script&gt;</pre><p>After that you will notice a couple of check for support of “fetch” and other functionalities also.</p><p>No matter that this skeleton will work on modern browsers: I suggests you to <strong>always check for the device’s capabilities</strong>.</p><p>Out there it’s full of strange older devices like old “Smart TV”, and crazy JVM; so it does not hurt to display them that their browser is too old.</p><p>Here it comes the <em>importmap</em>.</p><p>Here we specify to the JS engine where it can find any library that we will try to import.</p><p>The importmap in the HTML file <strong>declares dependencies directly</strong>, pointing to a <strong>bundled version</strong> of Preact, HTM, and Signal that can be loaded via script tags.</p><p><strong>This setup bypasses the need for a bundler or module loader</strong> during development.</p><pre> &lt;script type=&quot;importmap&quot;&gt;<br>      {<br>        &quot;imports&quot;: {<br>          &quot;preact&quot;: &quot;/app/dist/standalone.js&quot;,<br>          &quot;preact/hooks&quot;: &quot;/app/dist/standalone.js&quot;,<br>    &quot;htm&quot;: &quot;/app/dist/standalone.js&quot;,<br>          &quot;@preact/signals&quot;: &quot;/app/dist/standalone.js&quot;,<br>          &quot;@preact/signals-core&quot;: &quot;/app/dist/standalone.js&quot;,<br>          &quot;preact-router&quot;: &quot;/app/dist/preact-router.module.js&quot;<br>        }<br>      }<br><br> &lt;/script&gt;</pre><p>You can specify remote libraries aswell but I prefer to always use a local copy. Expecially if I’m going to bundle this webapp also into a mobile app using a WebView like Cordova.</p><p><em>Did you noticed standalone.js?</em></p><p>I’ve a few libraries packed all togheter in a single file. Thanks to this project:</p><p><a href="https://github.com/mujahidfa/preact-htm-signals-standalone">https://github.com/mujahidfa/preact-htm-signals-standalone</a></p><p><strong>In only 22KB</strong> I got Preact with hooks and signals + HTM template library.</p><p>Now we’re ready to bootstrap our main application component:</p><pre> &lt;script type=&quot;module&quot;&gt;<br>  import {h, render} from &#39;preact&#39;;<br>  import {MainApp} from &#39;/app/main.js&#39;;<br><br>  window.onload = function() {<br>   console.log(&#39;* onload&#39;);<br>   const app = h(MainApp);<br>   render(app, document.getElementById(&quot;main&quot;));<br>  }<br> &lt;/script&gt;<br>&lt;div id=&quot;main&quot;&gt;&lt;/div&gt;</pre><p>If you’re familiar with React this is just “<em>hello world</em>” for you.</p><p>Notice that the main component is into a separate file, <em>main.js</em>, loaded locally. The “/app/main.js” can actually be an absolute or relative URL, not a file path.</p><h4>The splash screen example</h4><p>AS bonus I’ve included a simple splash screen as an example of using “Signals”.</p><p>This will be visible while our application is initializing.</p><p>Make sure to put all the CSS and the HTML for your splash screen inside the main index.html (don’t relay on external files).</p><p>Also if possibile include image inline as <em>base64</em> to avoid any external HTTP request, otherwise we will loose the concept of “<em>preload</em>” here.</p><pre> &lt;style&gt;<br>  #preload {<br>   color: #6d1153;<br>   background: #fff;<br>   padding: 1rem;<br>   position: fixed;<br>   top: 0;<br>   left: 0;<br>   right: 0;<br>   bottom: 0;<br>   display: flex;<br>   flex-direction: column;<br>   align-items: center;<br>   justify-content: center;<br>   text-align: center;<br>   font-family: sans-serif;<br>   font-size: 18px;<br>   line-height: 1.7;<br>  }<br><br>  #preload img {<br>   display: block;<br>   margin-bottom: 2rem;<br>  }<br><br>  #preload p, #preload h1 {<br>   color: #333;<br>  }<br><br>  #preload a {<br>   color: #FF1978;<br>  }<br> &lt;/style&gt;<br>&lt;div id=&quot;preload&quot;&gt;<br> &lt;h1&gt;Loading...&lt;/h1&gt;<br> &lt;p&gt;For more info please visit: &lt;a href=&quot;https://www.antoniogallo.it/&quot;&gt;www.antoniogallo.it&lt;/a&gt;&lt;/p&gt;<br>&lt;/div&gt;</pre><p>In main.js I will show you in detail how this gets hidden.</p><h4>Developing with HTML instead of JSX</h4><p>And this is the “Main” component:</p><pre>import {html} from &#39;htm&#39;;<br>import Router, {route} from &#39;preact-router&#39;;<br>import {signal} from &#39;@preact/signals&#39;;<br>import {effect} from &#39;@preact/signals-core&#39;;<br><br>export function MainApp() {<br> console.debug(&#39;- render MainApp&#39;);<br> return html`<br>  ${$loading.value ? null : html`<br>   &lt;${Router}&gt;<br>    &lt;${PageHome} path=&quot;/&quot;/&gt;<br>    &lt;${PageNotFound} default/&gt;<br>   &lt;//&gt;`}<br> `;<br>}</pre><p>As you can see<strong> it does not use JSX but the HTM</strong> library.</p><p>HTM syntax is used for creating JSX-like components directly in the browser, and Signal manages the state changes reactively, updating the UI in response to user interactions.</p><p>In this component until $<em>loading</em> is <em>TRUE</em> then nothing will be rendered (<em>NULL</em>), i.e. our<em> div#preload </em>will be still visibile in place of <em>div#main.</em></p><p>When the loading is completed it will finally render our page according to the <em>preact-router</em>’s settings.</p><p>The <em>$loading</em> variable is an object created with<em> preact-signal</em>:</p><pre>export const $loading = signal(true);</pre><p>You can create your own initialization function to pre-populate your main component and finally <strong>set this variable to <em>FALSE</em></strong> when you’re finished:</p><pre>setTimeout(function(){<br> console.log(&#39;- loading completed.&#39;);<br> $loading.value = false;<br>}, 1000);</pre><p>Using “<em>effect </em>” and “<em>dispose</em>” we can intercept “<em>una tantum</em>” when <em>$loading</em> changes its value and hide the splash screen:</p><pre>const dispose = effect(() =&gt; {<br> if ($loading.value) {<br>  return;<br> }<br> console.debug(&#39;- app ready&#39;);<br> // Hide Preload<br> const preloadDiv = document.getElementById(&quot;preload&quot;);<br> preloadDiv.style.display = &quot;none&quot;;<br> dispose();<br>});</pre><p>Since <em>$loading</em> is used inside our “Main” component too, it will automatically trigger the re-render of our Main component.</p><h4>Creating a simple counter with Signal and HTM</h4><p>This is very straightforward example. First create your variable:</p><pre>export const $counter = signal(0);</pre><p>and then manipulate it with those two buttons:</p><pre>const PageHome = function() {<br> console.debug(&#39;- render PageHome&#39;);<br> return html`<br>  &lt;div class=&quot;page active &quot;&gt;<br><br>    &lt;div class=&quot;flex-row&quot;&gt;<br><br>     &lt;a class=&quot;btn&quot; onclick=&quot;${() =&gt; {<br>      $counter.value--;<br>     }}&quot;&gt;\u00AB&lt;/a&gt;<br><br>     &lt;span class=&quot;counter&quot;&gt;${$counter}&lt;/span&gt;<br><br>     &lt;a class=&quot;btn&quot; onclick=&quot;${() =&gt; {<br>      $counter.value++;<br>     }}&quot;&gt;\u00BB&lt;/a&gt;<br><br>    &lt;/div&gt;<br><br>  &lt;/div&gt;`;<br>};</pre><p>TIPS: in HTM you cannot use HTML entities like <em>&amp;raquo;</em> because those will be rendered as written. The trick is to use the \u0000 syntax instead.</p><p><em>So that’s all.</em></p><p>Feel free to add your Manifest.json and your favourite ServiceWorker implementation to create a full working PWA.</p><h4>Benefits of a Buildless Approach</h4><ul><li><strong>Simplicity</strong>: There’s<strong> no need to maintain webpack</strong>, Rollup, or other build configurations, which simplifies the setup and reduces the potential for build-related issues.</li><li><strong>Speed</strong>: <strong>Changes are reflected immediately</strong> upon refreshing the browser, speeding up the development cycle.</li><li><strong>Focus on Code</strong>: Developers can focus more on coding rather than on tooling, which is especially beneficial for small projects or during the initial stages of development.</li></ul><h4>Drawbacks to Consider</h4><ul><li><strong>Scalability</strong>: As the project grows, the lack of a build system might limit optimization options like tree shaking, minification, and advanced bundling strategies.</li><li><strong>Browser Compatibility</strong>: Relying heavily on modern browser features may limit the application’s compatibility with older browsers.</li></ul><h4>Conclusion</h4><p>Utilizing Preact, HTM, and Signals for a buildless JavaScript application provides an <strong>excellent balance between modern development practices and simplicity</strong>.</p><p>This approach is particularly appealing for developers looking to create <strong>lightweight applications or prototypes rapidly</strong>.</p><p>While it may <strong>not suit every project, especially large-scale applications</strong>, it offers a compelling option for those prioritizing quick setups and fewer dependencies.</p><h4>About Me</h4><p>I have got 40+ years experience in Open Source technologies. I am a Developer, Speaker and Trainer. Please check my <a href="https://www.linkedin.com/in/galloantonio/">Linkedin profile</a> or <a href="https://antoniogallo.it/">https://antoniogallo.it</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f99386ad36d4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Taming PHPWord to create complex documents]]></title>
            <link>https://medium.com/@antoniogallo.it/taming-phpword-to-create-complex-documents-2cc58629921c?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/2cc58629921c</guid>
            <category><![CDATA[amazon-kindle]]></category>
            <category><![CDATA[microsoft-word]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[docx]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Sun, 28 Jul 2019 11:41:21 GMT</pubDate>
            <atom:updated>2019-07-28T13:26:57.110Z</atom:updated>
            <content:encoded><![CDATA[<p>We usually create EPUB ebooks using Sigil and then we redo the whole editing work in LibreOffice to create a printable PDF version at 300DPI.</p><p>To avoid this I created a WordPress theme that leverage the Gutenberg editors and then, parsing all the blocks, generates a working EPUB file and a LibreOffice ODT file using <a href="https://github.com/PHPOffice/PHPWord">PHPWord</a>.</p><p>I assume you will read other basic tutorial and getting started about PHPWord, i’m not going to cover those topics. I wish you just to describe all the problems I found and how I did solved them.</p><h4>Output Format</h4><p>As soon as I tried to add more complex formatting beside simple paragraphs: images, tables, blocks i discovered that the PHPWord driver for exporting into LibreOffice’s format is not complete.</p><p>So, first advice i wish to give is to switch to DOCX.</p><pre>$objWriter = <strong>IOFactory</strong>::createWriter( $phpWord, &#39;Word2007&#39; );<br>$objWriter-&gt;save( $zipRealFileName );</pre><h4>Document Metadata</h4><p>Of course we are going to publish a book on Amazon so its important to fill-in all the document’s metadata:</p><pre>$properties = $phpWord-&gt;getDocInfo();<br>$properties-&gt;setCreator( get_ebook_author() );<br>$properties-&gt;setCompany( get_ebook_author() );<br>$properties-&gt;setTitle( get_ebook_title() );<br>$properties-&gt;setDescription( get_ebook_description() );<br>$properties-&gt;setCreated( get_ebook_date_created() );<br>$properties-&gt;setModified( time() );</pre><h4>Twips? What’s that?</h4><p>Almost all the function call of PHPWord requires a parameter to be expressed in twips. But no worries, there is a Converter class that has lots of static method that helps you to convert value from the correct source:</p><pre><strong>Converter</strong>::cmToTwip(2)<br><strong>Converter</strong>::pointToTwip( 1.75 )<br><strong>Converter</strong>::inchToTwip( 0.5 )<br><strong>Converter</strong>::pixelToTwip( 800)</pre><h4>Document Size</h4><p>Setting document is simply done with:</p><pre>$paper = <strong>new </strong>Paper();<br>$paper-&gt;setSize( &#39;A4&#39; );</pre><p>this will convert your constant into width and height using a lookup table. However only a few strings are configured by default.</p><p>However our book on Amazon is 8x10&quot; so I had to patch the Paper class to add support for that size.</p><p>After you have created the paper object then you can define the style’s settings for the default section of page:</p><pre>$duecm = <strong>Converter</strong>::cmToTwip( 2 );</pre><pre>$sectionMargins      = <strong>array</strong>(<em><br>   </em>&#39;marginTop&#39;    =&gt; $duecm,<br>   &#39;marginLeft&#39;   =&gt; $duecm,<br>   &#39;marginRight&#39;  =&gt; $duecm,<br>   &#39;marginBottom&#39; =&gt; $duecm,<br>   &#39;breakType&#39;    =&gt; &#39;continuous&#39;,<br>   &#39;pageSizeW&#39;    =&gt; $paper-&gt;getWidth(),<br>   &#39;pageSizeH&#39;    =&gt; $paper-&gt;getHeight(),<br>);</pre><p>Then use those settings to create the first section of the document:</p><pre>$section = $phpWord-&gt;addSection( $sectionMargins );</pre><h4>Default Font</h4><p>The default font is easy… the tricky part is that setDefaultFontSize() will not accept a float value, you have to edit the method and comment the line where they use intval() to convert the value to integer.</p><pre>$phpWord-&gt;setDefaultFontName( &#39;Libre Baskerville&#39; );<br>$phpWord-&gt;setDefaultFontSize( 8.5 );<br>$phpWord-&gt;setDefaultParagraphStyle( <strong>array</strong>(<br><em>   </em>&#39;lineHeight&#39; =&gt; 1.3,<br>) );</pre><h4>Titles / Headers</h4><p>To configure the default built-in styles of the document for the headers: Title, Title 1, etc. You just use addTitleStyle(). The first parameter is the header starting with “null” for the main one and then 1, 2, 3, etc.</p><pre>$phpWord-&gt;addTitleStyle( 3,<br>   <strong>array</strong>(<br>      &#39;name&#39;  =&gt; &#39;Droid Sans&#39;,<br>      &#39;size&#39;  =&gt; 15.5,<br>      &#39;color&#39; =&gt; &#39;e07743&#39;<br>   ),<br>   <strong>array</strong>(<br>      &#39;pageBreakBefore&#39; =&gt; <strong>false</strong>,<br>      &#39;spaceBefore&#39;     =&gt; <strong>Converter</strong>::cmToTwip( 0.8 ),<br>      &#39;spaceAfter&#39;      =&gt; <strong>Converter</strong>::cmToTwip( 0.2 ),<br>      &#39;keepNext&#39;  =&gt; <strong>true</strong>,<br>      &#39;lineHeight&#39; =&gt; 1,<br>   )<br>);</pre><h3>Fixing Elements</h3><p>Ok its now time to fix some weird issue I got with <a href="https://phpword.readthedocs.io/en/latest/elements.html">Elements</a>.</p><h4>Tables</h4><p>Those are probably the most complicated things. I managed to have them working but the formatting cannot be very complex. Converting HTML to tables didn’t worked, i.e. addHtml(). You have to use the method that I will explain below.</p><p>First of all we need a few styles.</p><p>Lets define a style for the table that is 100% width and centered:</p><pre>$stileTabella             = <strong>array</strong>(<br>   &#39;width&#39;            =&gt; 100 * 50,<br>   &#39;unit&#39;             =&gt; &#39;pct&#39;,<br>   &#39;cellMarginBottom&#39; =&gt; <strong>Converter</strong>::cmToTwip( 0.5 ),<br>   &#39;alignment&#39;        =&gt; <strong>JcTable</strong>::CENTER,<br>);</pre><p>If you want borders and a little more spacing also add:</p><pre>&#39;borderSize&#39;       =&gt; 1,<br>&#39;borderColor&#39;      =&gt; &#39;333333&#39;,<br>&#39;cellMargin&#39;       =&gt; <strong>Converter</strong>::cmToTwip( 0.5 ),<br>&#39;cellSpacing&#39;      =&gt; <strong>Converter</strong>::cmToTwip( 0.5 )</pre><p>Then we need a style for the rows:</p><pre>$stileRiga = <strong>array</strong>( &#39;tblHeader&#39; =&gt; <strong>false</strong>, &#39;cantSplit&#39; =&gt; <strong>false </strong>);</pre><p>Its important here:</p><ul><li>tblHeader — will make the table first row to repeat when the table is split onto another page;</li><li>cantSplit — will prevent the table from splitting, i.e. false=don’t split; true=split;</li></ul><p>For cells we don’t need particular styles, only if you want to add some general formatting, i.e. like centering them.</p><p>Ok to create a table the important thing to remember is to always give the cell a width value, the first parameter, if you use <strong>null</strong> as on some example it won’t work.</p><p>So the correct way is:</p><pre>$cellWidth = <strong>Converter</strong>::cmToTwip( 5 );</pre><pre>$table = $section-&gt;addTable( $stileTabella );<br>$table-&gt;addRow( <strong>null</strong>, $stileRiga );<br>$cell = $table-&gt;addCell( $cellWidth);<br>$cell-&gt;addText(&#39;this is my column content&#39;);</pre><p>So, its up to you to calculate the correct $cellWidth.</p><p>Well you have the pageWidth, remove the margin and then divide by the number of columns.</p><h4>Switching from one column to two column</h4><p>To switch to two column text layout and then back to one column, Ijust created two styles that needs to be applied to sections.</p><pre>$styleSingleColumn = <strong>array</strong>( &#39;breakType&#39; =&gt; &#39;continuous&#39;, &#39;colsNum&#39; =&gt; 1 );</pre><pre>$styleTwoColumn    = <strong>array</strong>(<br>   &#39;breakType&#39; =&gt; &#39;continuous&#39;,<br>   &#39;colsNum&#39;   =&gt; 2,<br>   &#39;colsSpace&#39; =&gt; <strong>Converter</strong>::cmToTwip( 0.5 )<br>);</pre><pre>$twoColSection = $section-&gt;addSection($styleTwoColumn);</pre><pre>$oneColSection = $twoColSection-&gt;addSection( $styleSingleColumn );</pre><h4>Image flow and captions</h4><p>In LibreOffice we have text flow of the image that can be “separated” like we when we use display:block on an IMG tag. In Docx this is not supported. The only way to achieve this is to create a table with a single row and a single cell and add the image and the caption inside of it.</p><h4>Avoid Breaking of Content</h4><p>Not sure if this work everywhere in the document but at least works for the image inside the table’s cell with its own caption. The trick is to use addTextRun() instead of addText() and use the “keepLines” option:</p><pre>$textrun = $cell-&gt;addTextRun( <strong>array</strong>(<br>   &#39;keepLines&#39; =&gt; <strong>true</strong>,<br>   &#39;alignment&#39; =&gt; &#39;center&#39;,<br>) );</pre><h4>Boxes with Borders</h4><p>Having boxes (container or text) with borders does not seems supported. The trick, again, is to use tables and add the border directly to the cells.</p><p>In this case we create a style for the cell:</p><pre>$cell = $table-&gt;addCell( $cellWidth, <strong>array</strong>(<br>   &#39;bgColor&#39;         =&gt; &#39;afded8&#39;,<br>   &#39;borderLeftColor&#39; =&gt; &#39;000000&#39;,<br>   &#39;borderLeftSize&#39;  =&gt; <strong>Converter</strong>::pointToTwip( 1.75 ),<em><br>   </em>&#39;valign&#39;          =&gt; &#39;center&#39;,<br>) );</pre><p>This is the result:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/521/1*HtjHAEuPAoLzm-hBgZ54Kw.png" /></figure><h4>HTML Lists</h4><p>When converting an HTML fragment with an UL or OL list i got the first line always breaking onto another line.</p><p>I found a workaround by just adding a text break before and after it:</p><pre>$section-&gt;addTextBreak( 1 );<strong><br>Html</strong>::addHtml( $section, $innerHtml, <strong>false</strong>, <strong>true </strong>);<br>$section-&gt;addTextBreak( 1 );</pre><h4>HTML Paragraph: strange whitespace</h4><p>I got very weird whitespace issue when converting HTML paragraps into the docx using:</p><pre><strong>Html</strong>::addHtml( $section, $innerHtml, <strong>false</strong>, <strong>true </strong>);</pre><p>It seems the addHtml() when parsing via DOMDocument has problem with the newlines that WordPress adds inside the HTML fragment.</p><p>I resolved by replacing back those newslines with spaces and removing duplicated whitespaces:</p><pre><strong>function </strong>fix_gutenberg_newlines($html) {<br>   $html = preg_replace(&quot;/\r/m&quot;, &quot;&quot;, $html);<br>   $html = preg_replace(&quot;/\n/m&quot;, &quot; &quot;, $html);<br>   $html  = preg_replace(&quot;/\s\s+/m&quot;, &quot; &quot;, $html);<br>   <strong>return </strong>$html;<br>}</pre><h4>TOC / INDEX</h4><p>This has been particularly hard to discover, it required a lot of reading of github’s issues.</p><p>First we need to set this properties:</p><pre>$phpWord-&gt;getSettings()-&gt;setUpdateFields( <strong>true </strong>);</pre><p>Then you need to create a numberingStyle:</p><pre>$phpWord-&gt;addNumberingStyle(<br>   &#39;hNum&#39;,<br>   <strong>array</strong>(<br>      &#39;type&#39;   =&gt; &#39;multilevel&#39;,<br>      &#39;levels&#39; =&gt; <strong>array</strong>(<br><em>         </em><strong>array</strong>( &#39;pStyle&#39; =&gt; &#39;Heading1&#39;, &#39;format&#39; =&gt; &#39;none&#39;, &#39;suffix&#39;    =&gt; &#39;nothing&#39;, ),<br>         <strong>array</strong>( &#39;pStyle&#39; =&gt; &#39;Heading2&#39;, &#39;format&#39; =&gt; &#39;none&#39;, &#39;suffix&#39;    =&gt; &#39;nothing&#39;, ),<br>         <strong>array</strong>( &#39;pStyle&#39; =&gt; &#39;Heading3&#39;, &#39;format&#39; =&gt; &#39;none&#39;,  &#39;suffix&#39;    =&gt; &#39;nothing&#39;, ),<br>      )<br>   )<br>);</pre><p>This will trigger the display of number in the TOC, otherwise they will not appear, but without displaying automatic numbers.</p><p>If you want to use them, just change into:</p><pre><em>array(&#39;pStyle&#39; =&gt; &#39;Heading1&#39;, &#39;format&#39; =&gt; &#39;decimal&#39;, &#39;text&#39; =&gt; &#39;%1&#39;),<br>array(&#39;pStyle&#39; =&gt; &#39;Heading2&#39;, &#39;format&#39; =&gt; &#39;decimal&#39;, &#39;text&#39; =&gt; &#39;%1.%2&#39;),<br>array(&#39;pStyle&#39; =&gt; &#39;Heading3&#39;, &#39;format&#39; =&gt; &#39;decimal&#39;, &#39;text&#39; =&gt; &#39;%1.%2.%3&#39;),</em></pre><p>Then you need to add “hNum” to your header’s style. Add:</p><pre>&#39;numStyle&#39;   =&gt; &#39;hNum&#39;,</pre><p>to the 2nd array of “$phpWord-&gt;addTitleStyle(…”.</p><p>The TOC work out of the box as in the official doc:</p><pre>$toc = $section-&gt;addTOC();<em><br></em>$toc-&gt;setMinDepth( 1 );<br>$toc-&gt;setMaxDepth( 3 );</pre><h3>Conclusion</h3><p>I hope that some of the issues that I run into while using PHPWord and that I described here has helped you to save countless hours.</p><p>I tried to think about other solutions to create an intermediate format (because the result needs to be manually edited before publishing) that can be converted into PDF, at the moment using PHPWord with DOCX seems the best option:</p><ul><li>no other libraries exists to create complex ODT or DOCX format;</li><li>Scribus could be a possibile solution but its document format is not fully described on their wiki;</li><li>LaTeX or Postscript are not something that normal user can edit;</li></ul><p>So, here we are :)</p><h3>About Antonio</h3><p>I have got 30+ years experience in Open Source technologies. I am a Developer, Speaker and Trainer. Please check my <a href="https://www.linkedin.com/in/galloantonio/?source=post_page---------------------------">Linkedin profile</a> or <a href="https://antoniogallo.it/?source=post_page---------------------------">https://antoniogallo.it</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2cc58629921c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why I developed my own Wordpress Theme engine]]></title>
            <link>https://medium.com/@antoniogallo.it/why-i-developed-my-own-wordpress-theme-engine-33be82c31a9f?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/33be82c31a9f</guid>
            <category><![CDATA[sass]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[wordpress-themes]]></category>
            <category><![CDATA[grid-layout]]></category>
            <category><![CDATA[vanillajs]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Wed, 24 Jul 2019 21:56:03 GMT</pubDate>
            <atom:updated>2019-07-26T11:51:30.164Z</atom:updated>
            <content:encoded><![CDATA[<h3>Why I developed my own WordPress Theme engine</h3><p>You can think of WordPress as blog engine that help non-technical peoples to design their own website without hassles using themes and plugin that are on the market.</p><p>But, there are also people like me, developers that hopefully know how to code, that love to <strong>customize it, to adapt it to the customer specific needs</strong>: to build quite amusing websites, not just blogs.</p><p>I’ve been through many WordPress themes in the latest years; i’ve learned the hard way that <strong>they may satisfy your urgent needs in the short time but in the long time they’re going to be hard to maintain</strong>.</p><p>Also, if you look at the source code of this themes you will panic. Almost 80–<strong>90% of the code is dedicated to support generic features that I will never need,</strong> or vendor’s marketing stuff (up-selling, cross-selling, newsletter, etc.) or, even worst, obfuscated code that will contain scary stuffs.</p><p>This also mean that <strong>your website is going to have a performance penalty</strong> due to the bigger source code, the many files the server has to read from the disks, the extra SQL queries that will <strong>slow down the database</strong>, etc.</p><p>Not to mention that all those themes and plugins inject extra CSS and JS files in your HTML page slowing down even more the overall loading time of the website when the customer browse it.</p><p>The “<em>cure”</em> that the market created is even more scary: more plugins! We now have plugins that introduce different kind of cache systems, that try to concatenate and minify CSS and JS files, etc. They are not strictly speaking a bad thing but I hope you understand that from my point of view its seems like curing a disease with the same disease.</p><p>So, the goals I tried to achieve developing my own theme engine are:</p><ul><li>remove all unwanted features from WP;</li><li>provide, out-of-the-box, only a minimum viable product;</li><li><strong>allow to opt-in for any extra features;</strong></li><li>reduce CSS and JS at the minimum; avoid JQuery;</li><li>provide built-in alternatives for the most common used plugins;</li></ul><p>However, the main goal I wish to achieve, it is to have<strong> a common playground for all the projects</strong> and then just use a child theme to decide witch features to enable and witch do not. About this, my experience says that <strong>it is better to start with something simple and then just add</strong>, instead of trying to remove stuffs from a complex thing.</p><p>But lets see what I did in the past 10 days…</p><p>… its a very long list of self referential features, I know. But I did it to try explain you even better what are the features that I expect to have from WordPress and why commercial themes are not good for my own use case.</p><h3>Unwanted Features</h3><p>For speed, security and other motivations I worked hard to clean-up some unwanted features off from WordPress as much as possibile.</p><p>In the front-end area:</p><ul><li><strong>cleaned up the HEAD</strong> tags from all the things not strictly required (feed links, rsd, wlw, generator, links, emojy, oEmbed, etc.);</li><li><strong>removed all the extra CSS</strong> (comment, default Gutenberg CSS, etc.) except the main style.css;</li><li>blocked some XML RPC and pingbacks;</li><li><strong>removed the admin bar</strong> (it causes the loading of unwanted CSS e JS in the head); an “edit” link will appear in the bottom right corner of the page if you’re logged in;</li><li><strong>comments</strong>, breadcrumbs and post’s meta are <strong>hidden</strong> by default;</li><li>the author’s archive and the attachment’s pages are disabled by default;</li></ul><p>In the admin area:</p><ul><li>removed menus that are not needed;</li><li>removed dashboard items that are not needed;</li><li>hide the <em>upgrade notice</em> for non-admin users;</li><li>custom <em>TinyMCE</em> button bars for the <em>classic editor</em>;</li><li>removed some Gutenberg blocks that are not needed;</li><li><strong>all the default image sizes (thumbnail, medium, medium_large, large) are removed</strong>, only the “post-thumbnail” and the “full” size are left; its up to you to use <em>add_size()</em> to restore them or add new fancy image’s sizes.</li></ul><h3>MVP Features</h3><p>Those are the main features that I wanted to be enabled by default inside the core theme engine.</p><p>In the front-end area:</p><ul><li><strong>revisited loop</strong> — almost all the elements of the page template and the loop can be overridden by using a custom action or a template file;</li><li><strong>error handling</strong> — if anything bad happen then an HTTP 503 status is generated, this will prevent the search engine to index your page with error and they will return later;</li></ul><pre>header( &#39;HTTP/1.1 503 Service Temporarily Unavailable&#39;, <strong>true</strong>, 503 );<br>header( &#39;Retry-After: 3600&#39; );</pre><ul><li><strong>site layout</strong> — you can choose the page layout from each different combination of the “content” plus 2 sidebars;</li><li><strong>lazy load CSS and JS</strong> — using <em>bp_register_lazyload_style</em> or <em>bp_register_lazyload_script</em> you can delay the loading of non-important files after the <em>DOMContentLoaded</em> event;</li><li><strong>headers and footer widgets</strong> — you can <em>filter</em> how many rows and cols of widgets you want in the header and footer: up to 3 rows and 4 columns, the widget’s content will adapt using <em>flexbox CSS </em>automatically; see this example:</li></ul><pre>bp_set_sidebars_header(2,2);<br>bp_set_sidebars_footer(2,2 );</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/1*RrlOPusmXItn6aFmItWLfA.png" /><figcaption>The above example will generate those widgets.</figcaption></figure><ul><li><strong>google fonts</strong> — by just adding the constant <em>GOOGLE_FONT</em> with the font’s url it will be lazy loaded and the meta tags for <em>dns-prefetch</em> and <em>preconnect</em> automatically generated;</li></ul><pre>&lt;link rel=&quot;dns-prefetch&quot; href=&quot;<a href="https://fonts.gstatic.com/">https://fonts.gstatic.com</a>&quot;/&gt;<br>&lt;link rel=&quot;preconnect&quot; href=&quot;<a href="https://fonts.gstatic.com/">https://fonts.gstatic.com</a>&quot; crossorigin=&quot;anonymous&quot;/&gt;</pre><ul><li><strong>google analytics</strong> — by just adding the constant <em>GOOGLE_ANALYTICS </em>with the GA’s ID then a snipped for loading it will be automatically included and the meta tags for <em>dns-prefetch</em> will be added automatically generated;</li><li><strong>redirect url</strong> — by adding a custom field called <em>external_url</em> the user will be redirected to that <em>url</em> when accessing the content;</li><li><strong>responsive video</strong> — videos are wrapped by a div with a CSS class that will make them responsive;</li><li><strong>preload or include main CSS</strong> — the main <em>style.css</em> can be included directly in the HEAD or <em>preloaded</em> using the new <strong><em>rel=”preload</em></strong><em>”</em> syntax;</li></ul><pre>&lt;link rel=&#39;preload&#39; id=&#39;framework-style-css&#39;  href=&#39;<a href="http://localhost:8000/wp-content/themes/tenerife-child/style.css?ver=1563713722">/wp-content/themes/child/style.css</a>&#39; type=&#39;text/css&#39; media=&#39;all&#39;  as=&#39;style&#39; onload=&#39;this.rel=&quot;stylesheet&quot;&#39; /&gt;<br>&lt;noscript&gt;&lt;link rel=&#39;stylesheet&#39; id=&#39;framework-style-css&#39;  href=&#39;/wp-content/themes/child/style.css&#39; type=&#39;text/css&#39; media=&#39;all&#39; /&gt;&lt;/noscript&gt;</pre><ul><li><strong>archive page with content and grid style</strong> — an archive’s page will try to look as a normal page using the <em>title</em> and the <em>description</em> as content; the elements in the loop can be styled using pre-built grid CSS rules;</li></ul><pre>.<strong>wrap </strong>{<br>   display: grid;<br>   grid-template-columns: 2<strong>fr </strong>1<strong>fr</strong>;<br>   grid-template-rows: auto 1<strong>fr</strong>;<br>}<br><br>.<strong>entry-title </strong>{<br>   grid-area: 1 / 1 / 2 / 2;<br>   padding-right: .5rem;<br>}<br><br>.<strong>entry-image </strong>{<br>   grid-area: 1 / 2 / 3 / 3;<br>}<br><br>.<strong>entry-summary </strong>{<br>   grid-area: 2 / 1 / 3 / 2;<br>   padding-right: .5rem;<br>}</pre><p>In the admin area:</p><ul><li><strong>support for custom logo, header and backgrounds</strong> using a widget or via the <em>customizer</em>;</li><li>support for <strong>Gutenberg’s special align</strong> style: left, wide and full;</li><li><strong>custom login page</strong> — the login page will use the website’s own logo and will link to the homepage’s <em>url</em> instead of wordpress.com;</li><li><strong>media page</strong> — will display the info about the media dimensions;</li><li><strong>pages admin</strong> — a column with the featured image will be displayed and also another column with the “<em>order</em>” value; pages will be sorted automatically using the “order” column; in the page editor you will aso be able to edit “excerpt” and “tags” like posts;</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dWRC6dvVRSR0ShskIA9_7g.png" /><figcaption>page admin example</figcaption></figure><ul><li><strong>posts admin</strong> — will have an extra column with the featured image;</li><li><strong>image sizes selector</strong> — if you add custom size via <em>add_image()</em> you’ll be able to select them where appropriate;</li><li><strong>PDF overwrite</strong> — when uploading a new PDF file it will override the old one, with the same filename, instead of creating a new version;</li><li><strong>maximum image upload</strong> — when uploading a large image it will be automatically reduced to the default size of 1920 pixel;</li></ul><h3>Extra Features</h3><p>There are more features available beside the one i mentioned above. Those are not included by default but can be automatically loaded via a filter or by calling “<em>bp_$type_require(…)”.</em></p><p>Those are organized in different categories:</p><ul><li><strong>lib &amp; class</strong> — library that contains functions (or classes) that can be used by some extra features but not required inside the core;</li><li><strong>shortcode, metabox, widget, custom-types</strong>— typical WordPress ecosystem elements;</li><li><strong>feature</strong> — those files contains lots of action hooks, filters and recall other stuffs to achieve complex features;</li></ul><p>Every category has its own special filter, or function, that a child theme can use to include or exclude them.</p><pre>bp_add_features( <strong>array</strong>(<br>   &#39;nav-menu-walker&#39;,<br>   &#39;excerpt-readmore&#39;,<br>   &#39;comments&#39;,<br>   &#39;breadcrumbs&#39;,<br>   &#39;child-pages&#39;,<br>   &#39;social-share&#39;,<br>   &#39;related-post&#39;,<br>   &#39;genesis-navigation&#39;,<br>   &#39;comments-open&#39;,<br>   &#39;admin-page-sort-order&#39;<br>) );</pre><pre>add_filter( &#39;bpf_preload_metaboxes&#39;, <strong>function </strong>( $list ) {<br>   $list[] = &#39;subtitle&#39;;<br>   <strong>return </strong>$list;<br>} );</pre><p>From all the once i developed i think its worth to mention those one:</p><ul><li><strong>metabox and widget class helpers</strong> — those will help to create metaboxes and widgets easily: all the component I built uses those classes;</li></ul><pre><strong>&lt;?php</strong><br><strong>require_once </strong>BP_THEME_DIR . &#39;/class/metabox.php&#39;;<br><strong>require_once </strong>BP_THEME_DIR . &#39;/class/field/textfield.php&#39;;<br><strong>require_once </strong>BP_THEME_DIR . &#39;/class/field/textareafield.php&#39;;<br><strong>require_once </strong>BP_THEME_DIR . &#39;/class/field/checkboxfield.php&#39;;<br><br><br><strong>class BadPenguinSeoMetaBox extends </strong>BP_Class_MetaBox {<br><br>   <strong>protected </strong>$mb_context = &#39;side&#39;;<br><br>   <strong>function </strong>__construct() {<br>      <strong>parent</strong>::__construct( &#39;badpenguin_seo_metabox&#39;, &#39;SEO&#39; );<br>   }<br><br>   <strong>protected function </strong>setup() {<br>      <strong>parent</strong>::setup();<br><br>      $this-&gt;addField( &#39;BadPenguinTextField&#39;, &#39;seo_title&#39; )-&gt;setLabel( &#39;Title&#39; )-&gt;setAttribute( &#39;size&#39;, 80 );<br>           <br><br>      $this-&gt;addField( &#39;BadPenguinTextareaField&#39;, &#39;seo_description&#39; )-&gt;setLabel( &#39;DescriptionTitle&#39; )-&gt;setAttribute( &#39;rows&#39;, 2 );<br><br>      $this-&gt;addField( &#39;BadPenguinCheckboxField&#39;, &#39;seo_robots_noindex&#39; )-&gt;setLabel( &#39;Disable robots&#39; )-&gt;setContent( &#39;Hide from google search engine&#39; );<br>   }<br><br>}<br><br><em>// Bootstrap<br></em><strong>new </strong>BadPenguinSeoMetaBox();</pre><ul><li><strong>navbar widget</strong> — allow to create <strong>a complete navbar with logo and mobile menù support </strong>(hambuger buttom + overlay menù) <strong>that works in pure CSS without JS;</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/470/1*kQCkowZjICqQHGEh-kByhQ.png" /><figcaption>navbar widget example</figcaption></figure><ul><li><strong>template shortcode</strong> — allow you to include any HTML template from the directory “parts”; if used programmatically via <em>bp_get_template_part()</em> (that replaces <em>get_template_part</em>) you can pass additional parameters to the template that can be reused inside of it;</li><li><strong>child pages</strong> — if a page has a hierarchy with children then, at the end of its content, a menù will appear with all of them; it can be used automatically or via a shortcode;</li><li>embed <strong>github’s gists</strong>;</li><li><strong>genesis navigation next/prev</strong> — inspired by a plugin for genesis, this will add <strong>two big arrows at the center of the screen to navigate to the next or previous content</strong>; if you don’t like it there is another simpler version that will display the two links at the end of the content;</li><li><strong>medium content</strong> — if enabled, it will try mimic the aspect of a medium.com post. I’ve already developed a test example in past before this one: <a href="https://github.com/badpenguin/mimic-medium-typography">https://github.com/badpenguin/mimic-medium-typography</a> ;</li><li><strong>page cover</strong> — the featured image will appear covering the whole <em>viewport</em> of the browser with title and <strong>subtitle, </strong>this is another extra <em>metabox</em> that will create a custom field that you can use to display it below the title;</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/984/1*1Op90lWv6Zq4IGfh1xJe_w.png" /><figcaption>#page-cover example</figcaption></figure><ul><li><strong>copyright</strong> — will block copy, right click and drag’n’drop of images since them are protected by copyright;</li></ul><h3>CSS Management</h3><p>Having a light CSS is critical. I’ve used many CSS’s frameworks in the past since the born of the <strong>12-columns grid systems</strong>: Twitter <strong>Bootstrap</strong> up to version 4, <strong>Foundation</strong> 4 and 5, <strong>UiKit</strong> (my favourite, check it at <a href="https://getuikit.com/">https://getuikit.com/</a> ); just to mentions only the most famous.</p><p>For those CSS frameworks the same problem I mentioned above about the WordPress commercial themes is still valid: <strong>they are huge because they need to support general purpose features</strong>.</p><p>They are no less then 70KB without counting their javascript dependency counterparts.</p><p>To avoid this I gathered all my SASS snippets and <strong>created my own customizable set of CSS rules: </strong><a href="https://github.com/badpenguin/vanilla-css"><strong>https://github.com/badpenguin/vanilla-css</strong></a></p><p>The resulting <em>style.css</em> of my first website that I developed with my own custom theme was so little, 9KB (theme style + vanilla CSS), that I decided to include <em>style.css</em> directly in the HEAD of the page.</p><p><strong>So, my theme’s main <em>style.css</em> is generated by style.scss that has some custom SASS and also include “some” of the feature from Vanilla’s CSS.</strong></p><p>And if you develop a <em>child theme</em> its easier to include it via SASS directly and sets the SASS variables to customize colors and other stuffs.</p><h3>JS without JQuery</h3><p>The same as CSS is replicated on the javascript side. The first goal is to avoid JQuery and this is done by simple unregistering it and avoiding any script that will use it as dependency.</p><p><strong>I developed </strong><a href="https://github.com/badpenguin/vanilla-js"><strong>https://github.com/badpenguin/vanilla-js</strong></a><strong> </strong>to implement at least a minimal lightbox feature for gallery or single images. It has a <em>core.js</em> mandatory script with some DOM utilities that I use then the rest is totally optional.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/637/1*c44WobwEOD_sbfoi0ZqYDg.png" /></figure><h3>Built-in plugins</h3><p>So, using both Vanilla CSS and Vanilla JS i created custom features that try to resemble the <strong>core</strong> functionalities of some of the most common plugins that I’m forced to use:</p><ul><li>SEO by Yoast</li><li>Image Watermark</li><li>Lightbox</li><li>Custom Related Post</li><li>WP Social Sharing</li></ul><p>and more.</p><h4>SEO Functionalities</h4><p>A SEO <em>metabox</em> will appear in the left Gutenberg ‘s sidebar. Here you can specify a custom title, description or a checkbox for disabling <em>robots</em> for that page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/260/1*hwmNxTyULcOmwyy485s8dQ.png" /></figure><p><strong>Indexing is automatically disabled by default for attachments, tags and any other archive’s page except categories</strong>. You can use a filter to customize it.</p><p>If a featured image is present then the minimum Open Graph’s meta tags for Twitter, Facebook and Pinterest will be generated.</p><p>You an see the source code used to generate the above <em>metabox</em> in the previous chapter where i was talking about “lib and classes”.</p><h4>Watermark Functionalities</h4><p>Inspired by “Image Watermark”, this is a very simple plugin that can generate image with a text watermark, customizable using a filter. The original image is back-upped so it can be restored if you want to remove the watermark. Images that are too small (usually below 640 pixel) will not be watermarked by default.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8Ff4xxRBdLAW8ZaiRqJOcQ.jpeg" /><figcaption>Watermarked image example</figcaption></figure><h4>Gallery Functionality</h4><p>I’ve created a simple <em>lightbox</em> that will open a modal and navigate through one or more image depending on your configuration. There are arrows and a close button to click or you can navigate with the keyboard or swiping. A simple cross fade animation is used to change the slide.</p><p>You can see it in action here: <a href="https://visitaretenerife.it/quando-andare-a-tenerife/">https://visitaretenerife.it/quando-andare-a-tenerife/</a></p><h4>Lazy Load Image Functionality</h4><p>Every image in the site’s content and in the footer will be configured automatically. This plugin renames the <em>src</em> and a <em>srcset</em> attribute of the images to prevent the browser from loading them. Then, using a javascript <em>intersectionObservable</em>, the images will be lazy loaded. When entering the <em>viewport</em> the initial <em>src</em> and <em>srcset</em> will be restored and the image will appear.</p><p>With website with long content and lots of larges images this will speed up greatly the initial load of the page.</p><h3>TOC — Table of Content</h3><p>This is another typical situation where you can use 40 lines of code instead of big complex plugins. I use a shortcode to place the TOC in the preferred position, usually after a few paragraph in a big page with multiple headers, and it will automatically create named anchor for every H2 and H3 headers and create an UL &gt; LI list with the index.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/456/1*Drnux7igvRecjVYN4g6snw.png" /><figcaption>Example of TOC on an italian website</figcaption></figure><h4>Other plugins</h4><p>And in the end i replaced other classic plugins:</p><ul><li><strong>related post</strong> — it just calculate the best 3 related posts and display them using a grid;</li><li><strong>social share</strong> — add a CSS-only <em>sharebox</em> that will link the most common social sharing urls: Facebook, Twitter, Pinterest, Linkedin; no javascript is used; no counter; no fuzzy.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/547/1*GGRwcATxf_FEtUDZMjXkpw.png" /><figcaption>ShareBox example</figcaption></figure><h3>Closing Words</h3><p>While looking back at this post I got a little scared about how much work I did in the latest 10 days during my free time.</p><p>I really hope that I’ve finally got <strong>the ultimate swiss-knife for all my projects that are based on WordPress. </strong>I trust that the “on demand” API I’ve developed will help me<strong> to maintain all the projects with a single code base </strong>where, with just a small child theme, I can decided what to include and what not.</p><p>At this time, July 2019, I plan to built 3 more websites in the next 2 months with this theme framework before to release the source code on <em>github</em>. I wish to clean up and polish it a little before to release it to the public.</p><p>You will no wonder that the temporary name I’m using for it is “Vanilla WP”, just to continue the “Vanilla” saga of <a href="https://en.wikipedia.org/wiki/KISS_principle">KISS</a> projects.</p><p>So, please, stay tuned and remember my motto: “<em>More Hard More Fun</em>”.</p><h3>About Antonio</h3><p>I have got 30+ years experience in Open Source technologies. I am a Developer, Speaker and Trainer. Please check my <a href="https://www.linkedin.com/in/galloantonio/">Linkedin profile</a> or <a href="https://antoniogallo.it/">https://antoniogallo.it</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=33be82c31a9f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating an optimized big static page website with Next.Js]]></title>
            <link>https://medium.com/@antoniogallo.it/creating-an-optimized-big-static-page-website-with-next-js-dc53e5d7f941?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/dc53e5d7f941</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[server-side-rendering]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[webpack]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Thu, 10 Jan 2019 20:59:16 GMT</pubDate>
            <atom:updated>2019-11-17T17:01:52.414Z</atom:updated>
            <content:encoded><![CDATA[<h3>Next.Js optimized static landing page howto</h3><p>As i am every day more engaged in React development i took the chance to rewrite my personal website with Next.Js.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/982/1*wsxSxe_DZD6ZSA6FHPKqEQ.png" /></figure><h3>I want reusable React’s components everywhere</h3><p>My goal is to have a common layer of React <strong>components to be used in multiple situations</strong>:</p><ul><li><strong>Cordova</strong> Hybrid Mobile Application</li><li>Progressive Web Application — <strong>PWA</strong></li><li>Classic SPA</li><li>Portal with Server Side Rendering (<strong>SSR</strong>)though a custom CRM</li><li>Static Web Site generated through a Headless CRM</li><li>Landing Pages and other small static web site</li></ul><p>It happens often that a customer may ask me to develop one or more of this project. So, its important to me, to <strong>reuse the same branded UI components</strong> and not just the business logic, through all of them.</p><h3>The “<em>pros</em>” of Next.JS</h3><p>I choosed Next.Js because it can be used for both SSR and also as Static Generator. <strong>It covers all my “use cases”</strong> where a simple Webpack config alone is not enough.</p><p>However it has been a little tricky to adapt it to generate Landing pages.</p><p>Next.Js is great in providing a smooth user experience for an SSR website but the cost of this its a certain amount of JS and CSS files to be loaded, and preloaded, at the first visit.</p><h3>The “<em>cons</em>” of Next.JS</h3><p>If you have one big single homepage or a landing page, then you are not interested in the preloading stuffs. You just want to “<em>convert”</em> the user and your focus is in making a better page.</p><p>This includes:</p><ul><li>optimizing CSS for rendering “above the fold” content as fast as possible;</li><li>minimize the number of HTTP requests;</li><li><em>lazy-load</em> images and other assets;</li></ul><h3>DISCLAIMER</h3><p>As going now into technical details, config files and source code then I assume that you already know or you have used Next.Js, so i will only explain the relevant parts below.</p><h3>Configuring Next.JS for Typescript and SASS</h3><p>If you want to use Typescript (.tsx files) and SASS (.scss file) you can do this by install a few Next.Js “<em>plugins</em>”.</p><p>Next.js has a config file, <strong>next.config.js, that can be used to install plugins and customize the webpack’s config.</strong></p><p>So my initial next.config.js looks like this:</p><pre><strong>const </strong><em>withPlugins </em>= require(&quot;next-compose-plugins&quot;);<br><strong>const </strong>withTypescript = require(&#39;@zeit/next-typescript&#39;);<br><strong>const </strong>withSass = require(&#39;@zeit/next-sass&#39;);<br><br>module.exports = <em>withPlugins</em>(<br>   [<br>      withTypescript,<br>      withSass<br>   ],<br>   { /* nextConfig options here */ }<br>);</pre><p>You can find here <a href="https://github.com/zeit/next-plugins">https://github.com/zeit/next-plugins</a> more plugins to install and documentation of the one used above.</p><p>With those two installed then I can create my homepage as index.tsx and my SASS file as style.scss.</p><h3>Copying static web assets into the webserver root</h3><p>Next.js has a “static” directory that allow you to have all the files stored there copied inside the build directory and available, once deployed to a webserver, with the url prefix “/static/”</p><p>But if you need to copy robots.txt, .htaccess and other files into the root of the webserver some trick is required.</p><p>In next.config.js we can add an option, in the nextConfig object, to hook the <em>exportPathMap</em> function and use that one to recursively copy the files stored in a directory choosed by us into the build directory.</p><p>This is the code i used:</p><pre><strong>const </strong>recursiveCopy = require(&#39;recursive-copy&#39;);</pre><pre>module.exports = <em>withPlugins</em>( [ ... ], <br>{<br>exportPathMap: <strong>async function </strong>(defaultPathMap, {dev, dir, outDir, distDir, buildId}) {<br><em>   </em><strong>await </strong>recursiveCopy(<em>join</em>(dir, &#39;src/htdocs/&#39;), outDir, {dot:<strong>true</strong>});<br>   <strong>return </strong>defaultPathMap<br>},</pre><p>Basically, this will copy all the files, including the one starting with a dot, from “src/htdocs/” into the final build directory.</p><h3>How to customize the Webpack’s config</h3><p>Again, in next.config.js we can add another callback to the <em>nextConfig</em> object, but this time to change directly the webpack’s config adding new features.</p><p>Indeed, <strong>Next.Js does not minify CSS files out of the box</strong>, so i’ve added the <strong>OptimizeCSSAssetsPlugin </strong>plugin to the its configuration.</p><p>Also <strong>i don’t want the JS and CSS splitted in multiple chunks</strong> of files because i’ve only a single page: there is no point in making multiple HTTP requests; so i’ve added the <strong>LimitChunkCountPlugin </strong>plugin to achieve this goal.</p><p>This is what i did:</p><pre><strong>const </strong>webpack = require(&#39;webpack&#39;);<br><strong>const OptimizeCSSAssetsPlugin </strong>= require(&quot;optimize-css-assets-webpack-plugin&quot;);<br><br>module.exports = <em>withPlugins</em>( [...],<br>   {<br><br>      webpack: (config, {dev, isServer}) =&gt; {</pre><pre><br>         <strong>if </strong>(isServer) {<br>            <strong>return </strong>config;<br>         }<br><br>         <strong>var </strong>isProduction = config.mode === &#39;production&#39;;</pre><pre><strong>         if </strong>(!isProduction) {<br>            <strong>return </strong>config;<br>         }</pre><pre>         config.plugins.push(<br>            <strong>new </strong>webpack.optimize.<strong>LimitChunkCountPlugin</strong>({<br>               maxChunks: 1,<br>            })<br>         );<br><br><em>         </em>config.optimization.minimizer.push(<br>            <strong>new OptimizeCSSAssetsPlugin</strong>({})<br>         );<br><br>         <strong>return </strong>config<br>      }<br>   }<br>);</pre><p>Well, as you see above, we have this <strong><em>webpack</em> funcion that gots called twice. This because Next.Js builds both server code and client code.</strong></p><p>I am only interested in tweaking the config for the client when running in production mode, so i just skip the settings if the conditions are not met.</p><p>Finally i added the two plugins, mentioned above, to the plugin list and into the new Webpack’s version 4 <em>minimizer</em> config.</p><h3>How to reduce the number of HTTP requests</h3><p>Ok what’s left now? When i load my homepage in production it loads a CSS files, 3 JS files in the footer and it also preloads a bunch of JS files.</p><p>I want to do 3 things now:</p><ul><li>stop preloading JS code for navigation because i’ve a single page;</li><li>load the CSS directly in the head, to save an HTTP request;</li><li>skip including those JS files because i don’t have React code on the client side and i want everything pre-rendered at server side anyway — in this case;</li></ul><p><strong>Next.Js does not have a simple way to do this, we have to extend and modify the behavior of the builtin <em>Head</em> components.</strong></p><p>Together with the pages in the <em>pages </em>directory i’ve a <em>_document.js</em> file. This file is used to create a custom layout, overriding the default one, and adding extra vanilla Javascript.</p><p>This is a reduced version of the one i use:</p><pre><strong>import Document</strong>, {<strong>Main</strong>, <strong>NextScript</strong>} <strong>from </strong>&#39;next/document&#39;;<br><strong>import InlineStylesHead from </strong>&quot;../src/components/InlineStylesHead&quot;;<br><em><br></em><strong>export default class CustomDocument extends Document </strong>{<br><br>   setFooterTags() {<br>      <strong>return </strong>{<br>         __html: `<br>var WebFontConfig = {<br>   google: {families: [ &#39;Fjalla+One|Open+Sans:400,500,700&#39; ]}<br>};<br>/* ... omissis ...  */<br>      `<br>      };<br>   }<br><br><em>   </em>render() {<br>      <strong>return </strong>(<br>         &lt;<strong>html </strong>lang=&quot;it&quot;&gt;<br>         &lt;<strong>InlineStylesHead</strong>/&gt;<br>         &lt;<strong>body</strong>&gt;<br>         &lt;<strong>Main</strong>/&gt;<br>         {<strong>false </strong>&amp;&amp; (&lt;<strong>NextScript</strong>/&gt;)}<br>         &lt;<strong>script </strong>dangerouslySetInnerHTML={<strong>this</strong>.setFooterTags()}/&gt;<br>         &lt;/<strong>body</strong>&gt;<br>         &lt;/<strong>html</strong>&gt;<br>      )<br>   }<br>}</pre><p>This override the default Next.Js document in a few parts:</p><ul><li>First — the <em>dangerouslySetInnerHTML </em>attribute is used to render plain javascript code, used for lazy loading webfonts, google analytics and other stuffs;</li><li>Second — the <em>NextScript</em> component is commented out, so the 3 JS files in the footer will not be included in the final output;</li><li>Thirth — the default <em>Head</em> component from <em>next/head</em> is replaced by a custom version that extend and override functionality of the original, its called <em>InlineStylesHead</em> and it will stop the output of the JS preload code and it will also include the CSS directly in the html instead of a separate file</li></ul><p>And in the end the final results is:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2iNAreLEWP_exLV8YXC69g.png" /></figure><p>And the audit results looks good too:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/803/1*Rm1V6yHNa97O7xc4DWKw7w.png" /></figure><h3>What’s Next?</h3><p>Well there is more to do.</p><p>Next step for sure will be to add a plugin to manage the optimization of the images from the static directory (actually are not optimized).</p><p>Probably i wish also to test if serving the fonts locally instead that from google server will make the rendering even faster or not.</p><p>I also want to try that if lazy loading all the images that are not strictly required above the fold or by the SEO has some benefits.</p><h4>Do you want more?</h4><p>Next.Js is not alone. Beside Gatsby there are a few more project to look into for inspieration or to build a React ecosystem above of it: <a href="https://github.com/developit/preact-cli">preact-cli</a> and <a href="https://github.com/GoogleChromeLabs/prerender-loader">prerender-loader</a></p><h4>Show me the Source</h4><p>You’ll find a working config with 2 test pages and some bonus component here:</p><p><a href="https://github.com/badpenguin/nextjs-static-generator-boilerplate">https://github.com/badpenguin/nextjs-static-generator-boilerplate</a></p><h3>About Me</h3><p>I have got 30+ years experience in Open Source technologies. In the latest years, I had become in love with React and mobile development. Please check my <a href="https://www.linkedin.com/in/galloantonio/">Linkedin profile</a> or <a href="https://antoniogallo.it/">personal website</a> or <a href="https://www.badpenguin.org/">Linux blog</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc53e5d7f941" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dear customer, I apologize but I can’t sign your NDA]]></title>
            <link>https://medium.com/@antoniogallo.it/dear-customer-i-apologize-but-i-cant-sign-your-nda-24230d786a59?source=rss-ab2ff435814d------2</link>
            <guid isPermaLink="false">https://medium.com/p/24230d786a59</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[nda]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[react-native]]></category>
            <dc:creator><![CDATA[Antonio Gallo]]></dc:creator>
            <pubDate>Thu, 16 Aug 2018 18:11:17 GMT</pubDate>
            <atom:updated>2019-01-10T20:54:53.412Z</atom:updated>
            <content:encoded><![CDATA[<h4>I wrote this story to help others to don’t make the same mistakes</h4><p>In August 2018 I had time to focus a lot on doing some self-marketing. I ended up doing a lot of quotes about developing <em>React Native</em> mobile Apps.</p><p>Usually, I just work using my personal network: <em>word of mouth</em> has always worked good for me, but I love to develop Apps and I wanted another App project as quickly as possible.</p><p><strong>I’ve been asked to sign an NDA on 100% of those quotes :-(</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*U9JAeUlMiBpBWa4D" /><figcaption>“A man in a shirt writing on printed sheets of paper” by <a href="https://unsplash.com/@helloquence?utm_source=medium&amp;utm_medium=referral">Helloquence</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Don’t get me wrong: I’m currently under NDA with 2 of my best customers and I also have signed some more in the past so, that has never been a problem to me but, <strong>in the long term, I’m starting to see that’s not paying off</strong>.</p><ol><li>First of all, <strong>there is no value</strong> for me into it: I’ve to pay a <strong>huge fee</strong> if I reveal any info but in exchange I get nothing;</li><li>Often I<strong> can’t get credits for my work</strong>: I can’t tell others that I’m working for a customer or on a project; <strong>my portfolio currently lacks some of the best projects I worked for</strong> and also cannot mention the customer’s logo/name on my website;</li><li>Some <strong>NDA have a very long duration</strong>, sometimes several years;</li><li><strong>Its a waste of time</strong> to read it every time and sometimes I’ve to translate them from English for my lawyer;</li><li><strong>I’ve always acted as much as professional as I could</strong> in my career: if you don’t trust my word than probably you should try to know me well;</li></ol><p>Isn’t that a little too much for a simple quote?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-JWxFIzyJBCUnYhp" /><figcaption>Photo by <a href="https://unsplash.com/@rawpixel?utm_source=medium&amp;utm_medium=referral">rawpixel</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Probably I’m a little nostalgic but <em>gentlemen word</em> still means a lot to me. I know there is a lot of people out there ruining the market but that’s not my case.</p><p>If you want me to provide some proof of something then “just ask”… except to sign that paper: I will not.</p><p>I strongly believe:</p><blockquote>1% of the success of a company is your idea, 99% is execution — <a href="https://medium.com/u/940c6ca7254d">Tyler McIntyre</a></blockquote><p>and I<strong>’m here to provide the best execution as I can</strong>.</p><h4>About Antonio</h4><p>I have got 30+ years experience in Open Source technologies. In the latest years, I had become in love with mobile development. Please check my <a href="https://www.linkedin.com/in/galloantonio/">Linkedin profile</a> or <a href="https://antoniogallo.it/">https://antoniogallo.it</a></p><h4>Reference</h4><ul><li><a href="https://medium.com/startup-frontier/why-i-wont-sign-an-nda-4193aeda7251">https://medium.com/startup-frontier/why-i-wont-sign-an-nda-4193aeda7251</a></li><li><a href="https://medium.com/startup-grind/ndas-are-for-losers-31dae14e7a92">https://medium.com/startup-grind/ndas-are-for-losers-31dae14e7a92</a></li><li><a href="https://medium.com/@kpfrahm/how-to-react-if-someone-with-a-startup-idea-wants-you-to-sign-an-nda-97bf9d2335a3">https://medium.com/@kpfrahm/how-to-react-if-someone-with-a-startup-idea-wants-you-to-sign-an-nda-97bf9d2335a3</a></li><li><a href="https://medium.com/@sebastian212000/nda-for-your-idea-how-about-lol-5d1a7329652e">https://medium.com/@sebastian212000/nda-for-your-idea-how-about-lol-5d1a7329652e</a></li><li><a href="https://medium.com/@mboezi/a-better-use-of-your-time-than-ndas-9f97fb24b014">https://medium.com/@mboezi/a-better-use-of-your-time-than-ndas-9f97fb24b014</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=24230d786a59" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>