<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://screenisland.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://screenisland.com/" rel="alternate" type="text/html" /><updated>2026-03-29T13:17:31+00:00</updated><id>https://screenisland.com/feed.xml</id><title type="html">ScreenIsland.com - Focused Software Development</title><subtitle>{&quot;en&quot; =&gt; &quot;We develop approachable software to achieve more.&quot;, &quot;de&quot; =&gt; &quot;Software-Entwicklung &amp; Digitalisierungsberatung.&quot;}</subtitle><entry><title type="html">Announcing Usetix - The Simplest Way to Sell Tickets</title><link href="https://screenisland.com/2026/01/12/announcing-usetix/" rel="alternate" type="text/html" title="Announcing Usetix - The Simplest Way to Sell Tickets" /><published>2026-01-12T00:00:00+00:00</published><updated>2026-01-12T00:00:00+00:00</updated><id>https://screenisland.com/2026/01/12/announcing-usetix</id><content type="html" xml:base="https://screenisland.com/2026/01/12/announcing-usetix/"><![CDATA[<h1 id="announcing-usetix">Announcing Usetix</h1>

<p>I am excited to introduce <strong><a href="https://usetix.io">Usetix</a></strong>, the simplest way to sell tickets for your events. After building software tools for developers with Sunnybox, I wanted to create something that anyone can use. No coding or design skills required.</p>

<h2 id="why-usetix">Why Usetix?</h2>

<p>Event organizers shouldn’t need to be developers to sell tickets online. Most ticketing platforms are either too complex, too expensive, or both. Usetix strips away the complexity and lets you focus on what matters: your event.</p>

<h2 id="what-you-get">What You Get</h2>

<ul>
  <li><strong>Conversion-optimized ticket shops</strong> that are beautiful, fast, and mobile-friendly</li>
  <li><strong>Stripe payments</strong> for secure, trusted payment processing</li>
  <li><strong>Digital wallet passes</strong> with Apple Wallet and Google Wallet integration</li>
  <li><strong>QR code scanner</strong> to check in attendees quickly</li>
  <li><strong>Sales analytics</strong> to see how your tickets are selling in real-time</li>
  <li><strong>Two-factor authentication</strong> to keep your account secure</li>
</ul>

<h2 id="simple-fair-pricing">Simple, Fair Pricing</h2>

<p>No setup fees. No monthly fees. You only pay when you sell: <strong>€1 per ticket</strong>. That’s it. No hidden costs, no credit card required to get started.</p>

<h2 id="get-started">Get Started</h2>

<ol>
  <li>Head over to <a href="https://usetix.io">Usetix.io</a></li>
  <li>Create your first event</li>
  <li>Start selling tickets in minutes</li>
</ol>

<p>I’d love to hear your feedback! Drop me a line at <a href="mailto:bijan@screenisland.com">bijan@screenisland.com</a> or find me on <a href="https://twitter.com/BijanRahnema">Twitter</a>.</p>

<p>Let’s make event ticketing effortless.</p>

<p><a href="https://twitter.com/BijanRahnema">Bijan</a></p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[Introducing Usetix, a simple event ticketing platform with Stripe payments, digital wallet passes, QR check-in, and sales analytics.]]></summary></entry><entry><title type="html">A native Tailwind alternative - Exploring Scoped CSS</title><link href="https://screenisland.com/2024/03/08/scoped-css/" rel="alternate" type="text/html" title="A native Tailwind alternative - Exploring Scoped CSS" /><published>2024-03-08T00:00:00+00:00</published><updated>2024-03-08T00:00:00+00:00</updated><id>https://screenisland.com/2024/03/08/scoped-css</id><content type="html" xml:base="https://screenisland.com/2024/03/08/scoped-css/"><![CDATA[<h1 id="a-native-tailwind-alternative---exploring-scoped-css">A native Tailwind alternative - Exploring Scoped CSS</h1>

<p>In the ever-evolving landscape of web development, the quest for efficient, scalable, and maintainable styling
solutions is perpetual. Among the myriad of frameworks and methodologies, utility-first frameworks like Tailwind
CSS have gained immense popularity for their ability to streamline the development process with their atomic
classes. However, with the advent of CSS variables and the upcoming feature of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@scope">scoped CSS</a>, there’s a burgeoning
alternative that promises a blend of flexibility, performance, and maintainability that could rival, and in some
aspects surpass, utility frameworks.</p>

<h2 id="see-the-experiment-chrome-only"><a href="/experiments/scopedcss.html">See the experiment (Chrome only)</a></h2>
<p>Please note that as of now only Chrome supports @scope selector. The soon to be released Safari 17.4 will also start supporting it soon leading to broad adaption having the two biggest engines (webkit and blink) on board.</p>

<p><img src="/assets/img/scopedcss.png" alt="Scoped CSS Experiment" width="1211" height="1499" loading="lazy" /></p>

<h2 id="the-power-of-css-variables">The Power of CSS Variables</h2>

<p>CSS variables, also known as custom properties, are pivotal in creating a dynamic, maintainable, and themable
styling system. They allow developers to define a value once and reuse it throughout the stylesheet,
facilitating ease in making global changes. For instance, a primary color can be set as a variable –
changing
it updates the color scheme across the entire project seamlessly. This not only reduces redundancy but also
enhances readability and maintainability.</p>

<p>CSS variables shine in their ability to be manipulated in real-time through JavaScript, opening doors to
interactive and responsive designs that adapt to user inputs or environmental changes without the overhead of
additional classes or inline styles.</p>

<h2 id="scoped-css-a-leap-towards-modular-styling">Scoped CSS: A Leap Towards Modular Styling</h2>

<p>Scoped CSS, a feature eagerly awaited by developers, introduces the ability to limit the scope of CSS styles
to specific components or sections of a webpage without the risk of global style leakage. This encapsulation
ensures that styles are localized, reducing unintended side-effects and making components more reusable and
easier to manage.</p>

<p>The combination of scoped CSS with CSS variables presents a compelling narrative for developers seeking a
balance between global theming and local component styling. It enables a modular approach where components
are styled independently yet adhere to a unified theme through shared variables.</p>

<h2 id="a-great-alternative-to-utility-frameworks">A Great Alternative to Utility Frameworks</h2>

<p>Utility frameworks like Tailwind CSS have their merits in rapid development and enforcing consistency.
However, they often lead to bloated HTML with extensive class usage and can obscure the semantic meaning behind the
markup. The alternative approach using CSS variables and scoped CSS addresses these concerns by keeping the
styling separate from the markup, leading to cleaner, more semantic HTML.</p>

<p>This combination promotes a design system that is both scalable and maintainable. By leveraging CSS
variables
for theming and scoped CSS for component-level styles, developers can create a cohesive look and feel while
maintaining the integrity of modular components. This approach also potentially offers better performance by
reducing the number of classes and the overall size of the stylesheet.</p>

<h2 id="conclusion">Conclusion</h2>

<p>As we look towards the future of web development, the emergence of CSS variables and scoped CSS as
alternatives to utility frameworks like Tailwind CSS represents a shift towards more semantic, maintainable, and
efficient styling practices. This paradigm encourages a balanced approach, leveraging the best of both global theming
and local encapsulation, fostering a development ecosystem that is both dynamic and robust. As these features
become more widely supported, they promise to redefine the standards of web styling for better.</p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[Exploring native CSS variables and scoped CSS as a maintainable alternative to utility-first frameworks like Tailwind CSS.]]></summary></entry><entry><title type="html">Go, WebSockets, HTMX, and DOM Morphing is fun</title><link href="https://screenisland.com/2023/12/11/go-websockets-htmx-morphing-is-fun/" rel="alternate" type="text/html" title="Go, WebSockets, HTMX, and DOM Morphing is fun" /><published>2023-12-11T00:00:00+00:00</published><updated>2023-12-11T00:00:00+00:00</updated><id>https://screenisland.com/2023/12/11/go-websockets-htmx-morphing-is-fun</id><content type="html" xml:base="https://screenisland.com/2023/12/11/go-websockets-htmx-morphing-is-fun/"><![CDATA[<h1 id="go-websockets-htmx-and-dom-morphing-is-fun">Go, WebSockets, HTMX, and DOM Morphing is fun</h1>

<h2 id="introduction">Introduction</h2>
<p>Real-time updates in web applications are a common requirement. Whether it’s a chat application, a dashboard, or a game, users expect to see changes in real-time without having to refresh the page manually. This can be achieved using WebSockets, but it often requires a lot of boilerplate code and can be difficult to implement correctly. In this blog post, we will explore how Go, WebSockets, HTMX, and DOM Morphing can make it easier to build real-time applications.</p>

<h2 id="the-problem">The Problem</h2>
<p>Traditionally, real-time updates using WebSockets involve sending raw data from the server to the client, which then updates the DOM accordingly. However, this approach can be inefficient and may require refreshing the entire page. We wanted to find a more efficient way to handle real-time updates, inspired by the concept of DOM morphing. DOM morphing involves sending a new DOM from the server to the client, which then surgically applies the necessary changes to the existing DOM. This approach is more efficient because it only modifies what is necessary and leaves the browser state intact.</p>

<h2 id="htmx-simplifying-dom-morphing-in-go">HTMX: Simplifying DOM Morphing in Go</h2>
<p>HTMX is a powerful library that simplifies the implementation of DOM morphing in Go applications. It handles all the details of managing the WebSocket connection and applying the changes to the DOM. With HTMX, developers can easily build real-time applications without having to write complex WebSocket code or worry about managing the DOM updates manually.</p>

<h2 id="building-a-todo-app-with-go-websockets-htmx-and-dom-morphing">Building a TODO App with Go, WebSockets, HTMX, and DOM Morphing</h2>
<p>To demonstrate the feasibility of this tech stack, we built a simple TODO app using Go, WebSockets, HTMX, and DOM Morphing. The app allows users to create and update tasks in real-time, and it supports multiple users, ensuring that changes made by one user are reflected in the UI for other users. You can see the app in action here:
<img src="/assets/img/todoapp.gif" alt="Todo App" width="800" height="480" loading="lazy" /></p>

<p>The source code for this tech-demo is available on GitHub: <a href="https://github.com/gobijan/go-htmx-todo">https://github.com/gobijan/go-htmx-todo</a></p>

<h2 id="why-choose-go-for-real-time-applications">Why Choose Go for Real-Time Applications?</h2>
<p>Go is an excellent choice for building real-time applications for several reasons. First, Go is known for its simplicity and efficiency. It has a small and concise syntax, making it easy to read and write code. This simplicity translates into faster development cycles, allowing developers to deliver high-quality solutions in a shorter timeframe.</p>

<p>Second, Go’s performance is exceptional. It is a compiled language designed to be fast and efficient. Go’s lightweight goroutines and built-in concurrency support enable developers to handle high loads and scale applications easily. This makes it an ideal choice for building robust and scalable real-time applications.</p>

<p>Additionally, Go has a strong standard library and a vibrant ecosystem of third-party packages. This provides developers with a wide range of tools and libraries that can accelerate development and enhance functionality. Whether it’s handling HTTP requests, working with databases, or implementing authentication and authorization, Go offers the necessary tools to build secure and reliable real-time applications.</p>

<h2 id="work-with-us">Work With Us</h2>
<p>At Screen Island, we have extensive experience in building APIs and backends using various tech stacks, including Go, WebSockets, HTMX, and DOM Morphing. We believe in using the best tools for each project based on its specific requirements and constraints. Our diverse skill set, which includes expertise in Ruby on Rails and C# with Dotnet, allows us to make informed decisions and deliver optimal solutions.</p>

<p>If you’re looking for a team with extensive experience in building real-time applications using Go, WebSockets, HTMX, and DOM Morphing, Screen Island is here to help. Contact us today to discuss your project requirements and let us bring your ideas to life.</p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[Building real-time web applications with Go, WebSockets, HTMX, and DOM morphing for seamless live updates.]]></summary></entry><entry><title type="html">Rails World 2023 Recap</title><link href="https://screenisland.com/2023/10/23/rails-world-2023-recap/" rel="alternate" type="text/html" title="Rails World 2023 Recap" /><published>2023-10-23T00:00:00+00:00</published><updated>2023-10-23T00:00:00+00:00</updated><id>https://screenisland.com/2023/10/23/rails-world-2023-recap</id><content type="html" xml:base="https://screenisland.com/2023/10/23/rails-world-2023-recap/"><![CDATA[<h1 id="rails-world-2023-recap">Rails World 2023 Recap</h1>

<p>It’s been two weeks since Rails World 2023 took place in Amsterdam, and I’ve finally found time to share my key takeaways from visiting the event.</p>

<h2 id="rails-renaissance">Rails Renaissance</h2>

<p>The economy has shifted significantly over the past three years. The era of abundant, cheap funding is over, and this change is manifesting itself in the tech industry. It’s now more crucial than ever to focus on developer productivity and impact. DHH kicked off the event with a thought-provoking slide showing a correlation between cheap funding and the rise of technologies like React, Webpack, and GraphQL.</p>

<p><img src="/assets/img/fed_rates_tech.png" alt="Fed Rates - Tech Correlation" width="1276" height="642" loading="lazy" />
<em>Picture of DHHs Opening Keynote showing fed rates and ‘unproductive tech’ correlation</em></p>

<p>This hits hard, especially when seeing tweets like this here where a developer mourns about the complexity of sending 5 simple form fields to a backend using React:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">You’d be shocked how complex a web form can be.<br /><br />I’m looking at a simple web form with 5 fields that uses:<br /><br />useState<br />useEffect<br />Redux<br />React Query<br />React Hook Form<br /><br />I’m trying to trace the data flow and my head is spinning.</p>&mdash; Cory House (@housecor) <a href="https://twitter.com/housecor/status/1714660338828714057?ref_src=twsrc%5Etfw">October 18, 2023</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>I’m not saying React is bad technology by any means. But I do believe that it’s overkill for many applications that are more or less just a CRUD interface.</p>

<h2 id="the-one-person-framework">The One-Person Framework</h2>

<p>When building <a href="https://sunnybox.io">Sunnybox.io</a>, I weighed different tech stacks. With more than 15 years of experience in web application development, I had a lot of options to consider. I ultimately chose Ruby on Rails for the same reasons many in the community do: it enables me to build a complete product solo. I can manage everything — from API, OpenAPI documentation, job backend, to user handling, payment integration and automated tests &amp; easy deployment — within a single, majestic monolith, without the need for a rigid frontend-backend division.</p>

<h2 id="going-forward">Going Forward</h2>

<p>Rails 7.1 was released on the day of the keynote, and I’ve already upgraded Sunnybox. While the update brings several quality-of-life improvements, the Rails World conference also previewed upcoming framework features. One of the most exciting revelations was the forthcoming DOM morphing capability, similar to what Phoenix Live View offers. This will significantly reduce the overhead for real-time updates that maintain scroll position and site state. For a sneak peek at what Turbo 8 will offer, check out this link:</p>

<p><a href="https://dev.37signals.com/a-happier-happy-path-in-turbo-with-morphing/">Dev 37signals Turbo 8 Preview</a></p>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>It was exhilarating to finally meet so many members of the Ruby on Rails community in person. Betting on Rails has proven to be a sound decision, and I look forward to continuing to build with this “boring,” stable, and highly productive technology.</p>

<p><img src="/assets/img/we_love_rails.jpg" alt="Bijan at Rails World 2023" width="1200" height="900" loading="lazy" />
<em>Me and a dear Ruby friend at Rails World 2023</em></p>

<p>Cheers,<br />
Bijan</p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[Key takeaways from Rails World 2023 in Amsterdam, including the Rails Renaissance, one-person framework philosophy, and Turbo 8 morphing.]]></summary></entry><entry><title type="html">Announcing the Sunnybox Public Beta</title><link href="https://screenisland.com/2023/09/08/sunnybox-public-beta/" rel="alternate" type="text/html" title="Announcing the Sunnybox Public Beta" /><published>2023-09-08T00:00:00+00:00</published><updated>2023-09-08T00:00:00+00:00</updated><id>https://screenisland.com/2023/09/08/sunnybox-public-beta</id><content type="html" xml:base="https://screenisland.com/2023/09/08/sunnybox-public-beta/"><![CDATA[<h1 id="-announcing-the-sunnybox-public-beta-">🌞 Announcing the Sunnybox Public Beta 🌞</h1>

<p>Hello, wonderful community! After months of hard work and invaluable feedback (huge shoutout to <a href="https://twitter.com/denkbox/" target="_blank">Lars</a> and <a href="https://twitter.com/Michael__Ga" target="_blank">Michael</a> 🙏), I am thrilled to unveil the <strong>Public Beta</strong> of Sunnybox. This is your opportunity to be among the first to experience what I’ve been working on and help shape its future.</p>

<h2 id="-what-is-sunnybox">🌈 What is Sunnybox?</h2>

<p>Sunnybox is a SaaS (Software as a Service) platform that I created to make your email interactions as seamless as possible. It wraps around IMAP and SMTP protocols and offers a JSON:API-compliant interface, complete with OpenAPI v3 documentation. Forget the intricacies of email protocols—jump straight into coding!</p>

<h2 id="-why-do-you-need-sunnybox">🤔 Why Do You Need Sunnybox?</h2>

<h3 id="simplify-email-integration">Simplify Email Integration</h3>
<p>Email communication is critical for businesses. However, there’s often a gap between the informal nature of emails and the structured data in ERP or CRM systems. That’s where Sunnybox comes in, providing a simplified pathway to integrate email data.</p>

<h3 id="accelerate-development">Accelerate Development</h3>
<p>No need to parse emails manually, scan for attachments, or juggle MIME types. Sunnybox gives you access to structured email data with minimal effort.</p>

<h3 id="ai-ready">AI-Ready</h3>
<p>With the growing role of AI in data analytics, I designed Sunnybox to make it easy to integrate email data into your AI workflows.</p>

<h2 id="-what-does-open-beta-mean">📝 What Does “Open Beta” Mean?</h2>

<p>I’m launching this Public Beta because I’m extremely interested in your feedback. I want to refine and enhance the service further, and your experience could be invaluable in that process. Rest assured, the API comes with comprehensive OpenAPI v3 and JSON:API documentation.</p>

<h2 id="️-whats-on-the-roadmap">🛣️ What’s on the Roadmap?</h2>

<p>Here’s what I’m planning to roll out next:</p>
<ul>
  <li>Webhooks for real-time notifications 🛎️</li>
  <li>AI triggers to make your workflows smarter 🤖</li>
  <li>Third-party integrations like n8n, ChatGPT, Zapier and more 🧩</li>
  <li>Auto-generated SDKs for popular programming languages 📚</li>
</ul>

<h2 id="-pricing-and-special-offers">💵 Pricing and Special Offers</h2>

<p>While I’m still finalizing the pricing, here’s the deal for now:</p>
<ul>
  <li><strong>Free Tier</strong>: You can connect up to two IMAP accounts for FREE during the beta.</li>
  <li><strong>Promo Code</strong>: Use the code <strong><code class="language-plaintext highlighter-rouge">sunnyboxbeta</code></strong> to explore other tiers for free for one month.</li>
  <li><strong>Early Adopters</strong>: If you join me early on this journey, special discounts will be available later on.</li>
</ul>

<h2 id="‍️-how-to-get-started">🙋‍♀️ How to Get Started?</h2>

<ol>
  <li>Head over to <a href="https://sunnybox.io">Sunnybox.io</a> and sign up.</li>
  <li>Dive into the <a href="https://sunnybox.io/docs/">API documentation</a>.</li>
  <li>Connect your IMAP accounts.</li>
  <li>Start coding and let the sun shine on your projects! 🌞</li>
</ol>

<p>I can’t wait to hear your feedback, issues, or any feature requests you might have. Feel free to share them on our <a href="https://github.com/screenisland/sunnybox-public/discussions">community forum</a>, <a href="https://github.com/screenisland/sunnybox-public/issues">public issue tracker</a> or email me directly at <a href="mailto:bijan@screenisland.com">bijan@screenisland.com</a>.</p>

<p>Let’s make email integration effortless, together! 🌬️</p>

<p><a href="https://twitter.com/BijanRahnema">Bijan</a></p>

<p>PS: Here are a few screenshots.</p>

<p><img src="/assets/img/sunnybox/beta/sunnybox-start.png" alt="Screenshot of Sunnybox Start" width="1647" height="1337" loading="lazy" />
<em>Screenshot of Sunnybox Start</em></p>

<p><img src="/assets/img/sunnybox/beta/sunnybox-providers.png" alt="Screenshot of Sunnybox Provider View" width="1647" height="1337" loading="lazy" />
<em>Screenshot of Sunnybox Provider View</em></p>

<p><img src="/assets/img/sunnybox/beta/sunnybox-apikeys.png" alt="Screenshot of Sunnybox API Keys View" width="1647" height="1337" loading="lazy" />
<em>Screenshot of Sunnybox API Keys View</em></p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[Sunnybox enters public beta — a hosted email API service for developers to fetch, send, and analyze emails through a single robust API.]]></summary></entry><entry><title type="html">Updates on Sunnybox (pivoted)</title><link href="https://screenisland.com/2023/07/04/updates-on-sunnybox/" rel="alternate" type="text/html" title="Updates on Sunnybox (pivoted)" /><published>2023-07-04T00:00:00+00:00</published><updated>2023-07-04T00:00:00+00:00</updated><id>https://screenisland.com/2023/07/04/updates-on-sunnybox</id><content type="html" xml:base="https://screenisland.com/2023/07/04/updates-on-sunnybox/"><![CDATA[<h1 id="sunnybox-is-now-an-api-driven-messaging-hub-starting-with-email">Sunnybox is now an API-driven Messaging Hub starting with Email</h1>

<p>When I started developing Sunnybox as an email client I soon realized that it’s quite annoying to handle emails.
The IMAP protocol is quite different from what you expect modern APIs to look like.</p>

<p>I cracked it but thought it would be a good idea to make it easier for others to build email integrations.
Out of this idea came the Sunnybox API.</p>

<p>It’s a REST API that allows you to send and receive emails with more analytical features, AI integration and webhooks on the way.</p>

<p><img src="/assets/img/sunnybox/sunnybox.webp" alt="Sunnybox API" width="2528" height="1518" loading="lazy" />
<em>Screenshots of Sunnybox API</em></p>

<p>Right now it is in developer-preview and you can see landingpage here:
<a href="https://sunnybox.app">https://sunnybox.app</a></p>

<p>I gave some friends access to the API and they are already building some cool stuff with it.
If you want to join the developer preview, please send me an email to <a href="mailto:hello@sunnybox.app">hello@sunnybox.app</a></p>

<p>Cheers Bijan</p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[How Sunnybox pivoted from an email client to an API-driven messaging hub for developers.]]></summary></entry><entry><title type="html">On building a new mail client</title><link href="https://screenisland.com/2022/12/01/developing-sunnybox/" rel="alternate" type="text/html" title="On building a new mail client" /><published>2022-12-01T00:00:00+00:00</published><updated>2022-12-01T00:00:00+00:00</updated><id>https://screenisland.com/2022/12/01/developing-sunnybox</id><content type="html" xml:base="https://screenisland.com/2022/12/01/developing-sunnybox/"><![CDATA[<h1 id="on-building-a-new-mail-client">On building a new mail client</h1>

<p>I almost died under an avalanche of emails when working at corporates, startups, and as a freelancer. Fed up with the conventional options for email and on the brink of declaring “defeat” on my inbox - I decided to tackle the problem head-on. 🏴‍☠️</p>

<p>I’m building a new mail experience: Sunnybox. It offers a fluid workflow, leaves the heavy-lifting to AI and does content aggregation. It thinks ahead for you and defuses the mines in your inbox while ensuring nothing falls through the cracks. Come as you are - Sunnybox supports @gmail.com, @me.com or @your-domain.com.</p>

<p>With Sunnybox, you own your data. It stays true to the IMAP standard while wrapping around and supercharging the default inbox.</p>

<p>I’m building this product because I want it to exist. I want something that I enjoy using and hope you might too.</p>

<p>I will use this blog to deliver progress updates.</p>

<p>You can further find more information on Sunnybox here:</p>

<p><a href="https://sunnybox.app">https://sunnybox.app</a></p>

<p>Cheers Bijan</p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[The story behind building Sunnybox, a new approach to email management born from frustration with conventional email clients.]]></summary></entry><entry><title type="html">Rails 7 Live JSON API</title><link href="https://screenisland.com/2022/10/05/ruby-on-rails-realtime-api/" rel="alternate" type="text/html" title="Rails 7 Live JSON API" /><published>2022-10-05T00:00:00+00:00</published><updated>2022-10-05T00:00:00+00:00</updated><id>https://screenisland.com/2022/10/05/ruby-on-rails-realtime-api</id><content type="html" xml:base="https://screenisland.com/2022/10/05/ruby-on-rails-realtime-api/"><![CDATA[<h1 id="rails-7-live-json-api">Rails 7 Live JSON API</h1>

<p>In this tutorial I want to show you how it’s possible to create a Ruby on Rails To-Do backend that is serving JSON via API and communicates changes in realtime to all connected clients via web sockets.</p>

<p>Let’s create a simple todo API. I will on purpose neglect all the other aspects like (authentication/authorization/validation) to keep this as pure as possible.</p>

<h2 id="setup">Setup</h2>
<p>First of all let us create our sample application:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails new todos-api <span class="nt">--api</span>
</code></pre></div></div>

<p>Now let us create our todo model:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails g scaffold todo name completed:boolean
rails db:migrate
</code></pre></div></div>

<p>I added two todos to our <em>seeds.rb</em>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Todo</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">name: </span><span class="s2">"First Task"</span><span class="p">,</span> <span class="ss">completed: </span><span class="kp">false</span> <span class="p">)</span>
<span class="no">Todo</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">name: </span><span class="s2">"Second Task"</span><span class="p">,</span> <span class="ss">completed: </span><span class="kp">false</span> <span class="p">)</span>
</code></pre></div></div>

<p>Now seed the database and start the server:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails db:seed
rails s
</code></pre></div></div>

<p>Congratulations we now have a simple todos API backend up and running that returns an array of our two seeded todos:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:3000/todos
</code></pre></div></div>

<h2 id="setting-up-the-channel">Setting up the channel</h2>
<p>Now we want to communicate changes to our todos as soon as they happen to connected JavaScript clients written in React, Ember, Angular, Svelte, Vue or any other fancy frontend-framework.</p>

<p>First we need to mount our websocket endpoint in our rails app. Therefore we modify our <em>development.rb</em> file and add these two lines. We don’t cover testing and prod here but you get the idea.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span><span class="p">.</span><span class="nf">action_cable</span><span class="p">.</span><span class="nf">disable_request_forgery_protection</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">config</span><span class="p">.</span><span class="nf">action_cable</span><span class="p">.</span><span class="nf">mount_path</span> <span class="o">=</span> <span class="s1">'/websocket'</span>
</code></pre></div></div>

<p>Well done. We expose now this websocket endpoint:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ws://localhost:3000/websocket
</code></pre></div></div>

<p>Now let’s create a channel we use to publish changes to our todos:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails g channel Todos
</code></pre></div></div>

<p>We keep this channel very simple:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TodosChannel</span> <span class="o">&lt;</span> <span class="no">ApplicationCable</span><span class="o">::</span><span class="no">Channel</span>
  <span class="k">def</span> <span class="nf">subscribed</span>
    <span class="n">stream_from</span> <span class="s2">"todos"</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="nf">unsubscribed</span>
    <span class="nb">puts</span> <span class="s2">"We lost a client."</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The subscribed method is invoked whenever a client connects. It sets the key the connected client is notified on. Here we simply chose “todos”.</p>

<p>You know what? We can now transmit data over the websocket to our connected clients:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">ActionCable</span><span class="p">.</span><span class="nf">server</span><span class="p">.</span><span class="nf">broadcast</span><span class="p">(</span><span class="s2">"todos"</span><span class="p">,</span> <span class="p">{</span><span class="ss">data: </span><span class="s2">"hello world"</span><span class="p">})</span>
</code></pre></div></div>

<h2 id="watching-the-todo-model-and-broadcasting-changes">Watching the Todo model and broadcasting changes</h2>

<p>There are many ways we could go here but I promised to keep it simple. ActiveRecord Models in Rails have callbacks for CRUD and we make use of these callbacks now to transmit the changes. We want to broadcast to every established websocket connection.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Todo</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
  <span class="n">validates</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">presence: </span><span class="kp">true</span>

  <span class="c1"># Hook into the create lifecycle</span>
  <span class="n">after_create</span> <span class="ss">:broadcast_create</span>

  <span class="kp">private</span>

  <span class="c1"># We broadcast that a new todo was created.</span>
  <span class="k">def</span> <span class="nf">broadcast_create</span>
    <span class="no">ActionCable</span><span class="p">.</span><span class="nf">server</span><span class="p">.</span><span class="nf">broadcast</span><span class="p">(</span><span class="s1">'todos'</span><span class="p">,</span> <span class="p">{</span>
      <span class="ss">data: </span><span class="p">{</span>
        <span class="ss">event: </span><span class="s2">"created"</span><span class="p">,</span>
        <span class="ss">todo: </span><span class="nb">self</span><span class="p">.</span><span class="nf">as_json</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Now our live server is done. Time for a minimal frontend to consume our API.</p>

<h2 id="connect-a-frontend-with-plain-websockets">Connect a frontend with plain websockets</h2>

<p>I just created a plain html file with javascript that outputs the received data:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!doctype html&gt;</span>
<span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">&gt;</span>
<span class="nt">&lt;head&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"viewport"</span>
        <span class="na">content=</span><span class="s">"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">"X-UA-Compatible"</span> <span class="na">content=</span><span class="s">"ie=edge"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"author"</span> <span class="na">content=</span><span class="s">"Bijan Rahnema"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;title&gt;</span>Screen Island Rails Todos API Demo-Frontend<span class="nt">&lt;/title&gt;</span>
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;body&gt;</span>
<span class="nt">&lt;script&gt;</span>
  <span class="kd">const</span> <span class="nx">onOpen</span> <span class="o">=</span> <span class="p">(</span><span class="nx">ws</span><span class="p">,</span> <span class="nx">store</span><span class="p">,</span> <span class="nx">code</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">WS OPEN</span><span class="dl">"</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">msg</span> <span class="o">=</span> <span class="p">{</span>
      <span class="na">command</span><span class="p">:</span> <span class="dl">'</span><span class="s1">subscribe</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">identifier</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span>
        <span class="na">channel</span><span class="p">:</span> <span class="dl">'</span><span class="s1">TodosChannel</span><span class="dl">'</span><span class="p">,</span>
      <span class="p">}),</span>
    <span class="p">};</span>
    <span class="nx">socket</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">msg</span><span class="p">));</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">msg</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="kd">const</span> <span class="nx">onClose</span> <span class="o">=</span> <span class="p">(</span><span class="nx">ws</span><span class="p">,</span> <span class="nx">store</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">WS CLOSE</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="kd">const</span> <span class="nx">onMessage</span> <span class="o">=</span> <span class="p">(</span><span class="nx">ws</span><span class="p">,</span> <span class="nx">store</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">evt</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ping</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">WS MESSAGE</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ws://localhost:3000/websocket</span><span class="dl">'</span>
  <span class="nx">socket</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WebSocket</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
  <span class="nx">socket</span><span class="p">.</span><span class="nx">onmessage</span> <span class="o">=</span> <span class="nf">onMessage</span><span class="p">(</span><span class="nx">socket</span><span class="p">);</span>
  <span class="nx">socket</span><span class="p">.</span><span class="nx">onclose</span> <span class="o">=</span> <span class="nf">onClose</span><span class="p">(</span><span class="nx">socket</span><span class="p">);</span>
  <span class="nx">socket</span><span class="p">.</span><span class="nx">onopen</span> <span class="o">=</span> <span class="nf">onOpen</span><span class="p">(</span><span class="nx">socket</span><span class="p">);</span>
<span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>
<p>Open your dev console and you can inspect incoming messages etc.</p>

<p>That’s it for this tutorial.</p>

<h2 id="wrap-up">Wrap-Up</h2>
<ol>
  <li>Create a Rails channel.</li>
  <li>Hook into model lifecycle event.</li>
  <li>Consume event’s in your new realtime JSON API.</li>
</ol>

<p>Cheers
<a href="https://twitter.com/BijanRahnema">@BijanRahnema</a></p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[Tutorial on building a Ruby on Rails To-Do backend with live JSON API and real-time WebSocket updates to all connected clients.]]></summary></entry><entry><title type="html">Back to Screen Island</title><link href="https://screenisland.com/2022/10/03/site-relaunch/" rel="alternate" type="text/html" title="Back to Screen Island" /><published>2022-10-03T00:00:00+00:00</published><updated>2022-10-03T00:00:00+00:00</updated><id>https://screenisland.com/2022/10/03/site-relaunch</id><content type="html" xml:base="https://screenisland.com/2022/10/03/site-relaunch/"><![CDATA[<h1 id="back-to-screen-island">Back to Screen Island</h1>

<p>A site relaunch was long overdue. In fact over the last years we’ve been busy working on customer projects. So the website didn’t get the priority it deserved.</p>

<p>This new site is built with <a href="https://jekyllrb.com">Jekyll</a>, our own version of <a href="https://tachyons.io">Tachyons</a> (with support for darkmode) and hosted on <a href="https://pages.github.com">GitHub Pages</a>.</p>

<p>When creating a website nowadays there are tons of low- or no-code options available with a plethora prebuilt of themes.
As a software company we believe though that the process of creating your own site is something very personal that deserves more than a default theme.</p>

<p>We wanted something that is integrating with our own workflow and aligns with our <a href="https://screenisland.com/#technology">sustainable tech stack mentality</a>.</p>

<p>Jekyll fits in nicely here as it was developed by <a href="https://en.wikipedia.org/wiki/Jekyll_(software)">GitHub’s Co-Founder</a> and is written in one of our favourite languages <a href="https://www.ruby-lang.org/en/">Ruby</a>. The first release was in 2008 and it is still actively maintained today. As Jekyll renders static HTML files one does not depend on any special server technology. In fact these pure HTML files will probably be readable till the end of the Internet.</p>

<p>You see? Very sustainable technology.</p>

<p>Same goes for Tachyons. The godfather of utility css frameworks. Yes we also considered Tailwind and we like tailwind but we felt for this site Tachyons is what we wanted. Something that is basically finished, can be easily extended and doesn’t need any further tech (compiler etc.).</p>

<p>Stay tuned for more content as we will blog regularly here.</p>]]></content><author><name>Bijan Rahnema</name></author><summary type="html"><![CDATA[Screen Island relaunches its website after years focused on client projects, with a fresh look and renewed direction.]]></summary></entry></feed>