<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="/rss.xsl" media="all"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Roastidio.us Tagged with web</title>
<link>https://roastidio.us/tag/2691</link>
<atom:link href="https://roastidio.us/tagged_with/web" rel="self" type="application/rss+xml"></atom:link>
<description>Roastidio.us Tagged with web</description>
<item>
<title>Edge 152 starts Microsoft’s two-week update race</title>
<link>https://4sysops.com/archives/edge-152-starts-microsofts-two-week-update-race/</link>
<guid isPermaLink="false">FmMqLyMqgfa8TE9Gp5aEj1bJ6y0ju5MUSPzh_Q==</guid>
<pubDate>Sun, 30 Aug 2026 23:35:55 +0000</pubDate>
<description>Microsoft Edge 152 begins a faster major-release schedule that will deliver browser updates every two weeks instead of every four. The change also affects WebView2, while organizations that need more time to test releases can remain on Edge’s eight-week Extended Stable channel. Source</description>
<content:encoded>&lt;div&gt;&lt;img src=&quot;https://4sysops.com/wp-content/uploads/2026/08/edge-152-starts-microsofts-two-week-update-race.jpg&quot; alt=&quot;Edge 152 starts Microsoft’s two-week update race&quot; title=&quot;&quot;/&gt;&lt;/div&gt;Microsoft Edge 152 begins a faster major-release schedule that will deliver browser updates every two weeks instead of every four. The change also affects WebView2, while organizations that need more time to test releases can remain on Edge’s eight-week Extended Stable channel. &lt;p&gt;&lt;a href=&quot;https://4sysops.com/archives/edge-152-starts-microsofts-two-week-update-race/&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Microsoft Edge 152 automatically blocks scam notification traps – 4sysops</title>
<link>https://4sysops.com/archives/microsoft-edge-152-automatically-blocks-scam-notification-traps/</link>
<enclosure type="image/jpeg" length="0" url="https://4sysops.com/wp-content/uploads/2026/08/microsoft-edge-152-automatically-blocks-scam-notification-traps.jpg"></enclosure>
<guid isPermaLink="false">g3OoiNFnV48LNGWpn1ge1qCWvxyPG3dlCZOpSA==</guid>
<pubDate>Sun, 30 Aug 2026 23:35:55 +0000</pubDate>
<description>Microsoft Edge 152 can now automatically unsubscribe users from push notifications sent by websites that Microsoft Defender SmartScreen blocks as scams, phishin</description>
<content:encoded>&lt;section&gt;

&lt;div&gt;
		&lt;h1&gt;Microsoft Edge 152 automatically blocks scam notification traps&lt;/h1&gt;&lt;div&gt;By &lt;span&gt;			&lt;a href=&quot;https://4sysops.com/archives/author/it-news/&quot;&gt;
				&lt;span&gt;
				IT News			&lt;/span&gt;
			 &lt;span&gt;AI&lt;/span&gt;&lt;/a&gt;
		&lt;/span&gt;

		 / &lt;span&gt;&lt;span&gt; Fri, Aug 28 2026 &lt;/span&gt;&lt;/span&gt; / 			&lt;span&gt;
				&lt;a href=&quot;https://4sysops.com/archives/microsoft-edge-152-automatically-blocks-scam-notification-traps/#respond&quot;&gt;Leave a Comment&lt;/a&gt;			&lt;/span&gt;

			&lt;/div&gt;&lt;div&gt;&lt;p&gt;Microsoft Edge 152 can now automatically unsubscribe users from push notifications sent by websites that Microsoft Defender SmartScreen blocks as scams, phishing pages, or malware. The Stable release also completes Edge’s shift to a two-week major-release cycle and adds Apple account sign-in on Windows and macOS.&lt;/p&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://4sysops.com/join/&quot;&gt;Read 4sysops without ads and access many member features.&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;
    	&lt;/div&gt;
&lt;/section&gt;&lt;div&gt;
		&lt;div&gt;
		

	&lt;div&gt;

		
					&lt;main&gt;
				

&lt;article&gt;

	
	
&lt;div&gt;

	
	
	
	&lt;div&gt;

		
		&lt;h2&gt;SmartScreen cuts off malicious notifications&lt;/h2&gt;
&lt;p&gt;When SmartScreen identifies a dangerous site, Edge stops it from sending further notifications and informs the user. Users can review blocked notification sources and restore permissions if a site was flagged incorrectly, although Microsoft is enabling the protection gradually.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://4sysops.com/wp-content/uploads/2026/08/microsoft-edge-152-automatically-blocks-scam-notification-traps.jpg&quot;&gt;&lt;img src=&quot;https://4sysops.com/wp-content/uploads/2026/08/microsoft-edge-152-automatically-blocks-scam-notification-traps-600x338.jpg&quot; alt=&quot;Microsoft Edge 152 automatically blocks scam notification traps&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;Microsoft Edge 152 automatically blocks scam notification traps (image neowin.net)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This closes a common abuse path in which legitimate browser notification permissions are later used to deliver scam alerts or links to malware. Edge 152 also includes a substantial set of security fixes.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://4sysops.com/join/&quot;&gt;Read 4sysops without ads and access many member features.&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;
    
&lt;h2&gt;More controls for administrators&lt;/h2&gt;
&lt;p&gt;Organizations can disable Apple account sign-in through policy. Administrators also gain additional controls over Copilot Cowork actions, including whether the assistant may open tabs, capture screenshots, or interact with web pages for users.&lt;/p&gt;
&lt;h2&gt;Other Edge 152 changes&lt;/h2&gt;
&lt;p&gt;The password-key button in the address bar now opens a credential panel for copying and managing saved passwords, with support for adding notes to logins. Microsoft has removed Drop and is gradually retiring real-time video translation.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://4sysops.com/join/&quot;&gt;Read 4sysops without ads and access many member features.&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;
    
&lt;p&gt;Stable updates will continue rolling out progressively. Users can check for version 152.0.4191.52 under Settings &amp;gt; About Microsoft Edge.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.neowin.net/news/microsoft-edge-152-adds-a-useful-feature-for-fighting-scam-notifications/?utm_source=rss&quot;&gt;Microsoft Edge 152 adds a useful feature for fighting scam notifications | neowin.net&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;div&gt;&lt;a href=&quot;https://4sysops.com/join/&quot;&gt;Read 4sysops without ads and access many member features.&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;
    
		
		
			&lt;/div&gt;
&lt;/div&gt;

	
&lt;/article&gt;

    
    &lt;div&gt;    &lt;div&gt;
                    &lt;div&gt;
                            &lt;/div&gt;
            &lt;div&gt;
                &lt;div&gt;
                                            &lt;div&gt;
                             &lt;span&gt;Subscribe&lt;/span&gt;
                            
                        &lt;/div&gt;
                                            &lt;div&gt;
                                                &lt;div&gt;
                            &lt;a href=&quot;https://4sysops.com/wp-login.php?redirect_to=https%3A%2F%2F4sysops.com%2Farchives%2Fmicrosoft-edge-152-automatically-blocks-scam-notification-traps%2F&quot;&gt; Login&lt;/a&gt;                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
                                                    &lt;div&gt;
                                                    
                                &lt;div&gt;Notify of &lt;/div&gt;
                                &lt;div&gt;
                                    
                                &lt;/div&gt;
                                                                    &lt;div&gt;
                                        
                                    &lt;/div&gt;
                                                                    &lt;div&gt;
                                    
                                &lt;/div&gt;
                                                            
                                                &lt;/div&gt;
                            &lt;div&gt;
                                        
                                        &lt;div&gt;
                        &lt;div&gt;
                            &lt;div&gt;
                                                                                                        &lt;div&gt;
                                        &lt;img src=&quot;https://secure.gravatar.com/avatar/e27485622011e286cd16c7410f88625ee5c9b0332c71f522b660beac5e62d5fd?s=56&amp;amp;d=robohash&amp;amp;r=g&quot; alt=&quot;guest&quot; title=&quot;&quot;/&gt;                                    &lt;/div&gt;
                                                &lt;div&gt;
                
                Label
                
                
                        &lt;div&gt;
                            
                                
                                
                                
                                
                                
                                
                                
                                
                            &lt;div&gt;
                            &lt;/div&gt;
        &lt;/div&gt;
                    &lt;/div&gt;
                                        &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;div&gt;
                        &lt;div&gt;
                                                        
                        &lt;/div&gt;
                                &lt;div&gt;
                    &lt;div&gt;
                        &lt;div&gt;
                                    &lt;div&gt;
                    &lt;/div&gt;
                                    
                Name*
                            &lt;/div&gt;
                        &lt;div&gt;
                                    &lt;div&gt;
                    &lt;/div&gt;
                                    
                Email*
                            &lt;/div&gt;
                    &lt;/div&gt;
                &lt;div&gt;
                    &lt;div&gt;
                                                
                        
                        &lt;span&gt;
                                &lt;span&gt;
                                    
                                    
                                &lt;/span&gt;
                            &lt;/span&gt;
                    
                            
        &lt;/div&gt;
                &lt;/div&gt;
                    
        &lt;/div&gt;
                            &lt;/div&gt;
                                        
                    &lt;p&gt;Δ
&lt;/p&gt;

&lt;div&gt; Subscribe to Newsletter&lt;/div&gt;

                
                        &lt;/div&gt;
                &lt;div&gt;
                    &lt;div&gt;
                            
                
                                        
                                        &lt;div&gt;
                        &lt;div&gt;
                            &lt;div&gt;
                                                                                                        &lt;div&gt;
                                        &lt;img src=&quot;https://secure.gravatar.com/avatar/c0434eab0e4d7f5c501902ac11ad26ef123a445b51b905c1e78eaf327fff2c2f?s=56&amp;amp;d=robohash&amp;amp;r=g&quot; alt=&quot;guest&quot; title=&quot;&quot;/&gt;                                    &lt;/div&gt;
                                                &lt;div&gt;
                
                Label
                
                
                        &lt;div&gt;
                            
                                
                                
                                
                                
                                
                                
                                
                                
                            &lt;div&gt;
                            &lt;/div&gt;
        &lt;/div&gt;
                    &lt;/div&gt;
                                        &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;div&gt;
                        &lt;div&gt;
                                                        
                        &lt;/div&gt;
                                &lt;div&gt;
                    &lt;div&gt;
                        &lt;div&gt;
                                    &lt;div&gt;
                    &lt;/div&gt;
                                    
                Name*
                            &lt;/div&gt;
                        &lt;div&gt;
                                    &lt;div&gt;
                    &lt;/div&gt;
                                    
                Email*
                            &lt;/div&gt;
                    &lt;/div&gt;
                &lt;div&gt;
                    &lt;div&gt;
                                                
                        
                        &lt;span&gt;
                                &lt;span&gt;
                                    
                                    
                                &lt;/span&gt;
                            &lt;/span&gt;
                    
                            
        &lt;/div&gt;
                &lt;/div&gt;
                    
        &lt;/div&gt;
                            &lt;/div&gt;
                                        
                    &lt;p&gt;Δ
&lt;/p&gt;

&lt;div&gt; Subscribe to Newsletter&lt;/div&gt;

                
                        &lt;/div&gt;
                &lt;/div&gt;
                    &lt;/div&gt;
                        &lt;div&gt;
                &lt;div&gt;
                    &lt;div&gt;
                        &lt;span&gt;0&lt;/span&gt; Comments                    &lt;/div&gt;
                                        
                    &lt;div&gt;
                                            &lt;/div&gt;
                    
                &lt;/div&gt;
                                &lt;div&gt;
                                        &lt;div&gt;
                                            &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
                &lt;/div&gt;
        &lt;/div&gt;
    
    
    			&lt;/main&gt;
			
		
	&lt;/div&gt;


	&lt;/div&gt; 
	&lt;/div&gt;&lt;footer&gt;
			&lt;div&gt;
	&lt;div&gt;
					&lt;div&gt;
											&lt;div&gt;
								&lt;div&gt;
				&lt;div&gt;&lt;p&gt;Copyright 4sysops © 2016 - 2026&lt;/p&gt;
&lt;/div&gt;			&lt;/div&gt;
						&lt;/div&gt;
										&lt;/div&gt;
			&lt;/div&gt;

&lt;/div&gt;
	&lt;/footer&gt;</content:encoded>
</item>
<item>
<title>HSK Manga: Turning Manga into Beginner-Level Chinese</title>
<link>https://hskmanga.com/blog/</link>
<enclosure type="image/jpeg" length="0" url="https://hskmanga.com/blog/img/hero-before-after.jpg"></enclosure>
<guid isPermaLink="false">7ANfxZLhM-nSbH8e-1aHYjVXbEDqT8qpSSbs6w==</guid>
<pubDate>Sun, 30 Aug 2026 22:53:32 +0000</pubDate>
<description>How manga chapters become graded Chinese readers: OCR on manga pages, logit-bias simplification, and text inpainting.</description>
<content:encoded>&lt;span&gt; Blog&lt;/span&gt;&lt;h1&gt;HSK Manga: Turning Manga into Beginner-Level Chinese&lt;/h1&gt;&lt;p&gt;By &lt;a href=&quot;https://hskmanga.com/about&quot;&gt;Jerry Rao&lt;/a&gt; · August 2026&lt;/p&gt;&lt;p&gt;
    My brother has been learning Chinese for years. He reads a lot of English-translated
    manhua, but when he tried reading the Chinese original, he grew frustrated. He still
    couldn&amp;#39;t parse many sentences and found himself spending more time looking up
    definitions than actually reading the text! Turns out this is a fairly normal
    experience. Research shows that you need to know about 95% of the words to follow
    text with assistance and 98% to read it comfortably
    (&lt;a href=&quot;https://nflrc.hawaii.edu/rfl/item/43&quot;&gt;Hu &amp;amp; Nation, 2000&lt;/a&gt;).
  &lt;/p&gt;&lt;p&gt;
    One solution for this is graded readers, hand-written text designed to target
    specific HSK levels (HSK is the standard 1–9 scale of Chinese proficiency). But
    writing them by hand doesn&amp;#39;t scale, and engaging graded text is hard to come by.
    That&amp;#39;s why I built &lt;a href=&quot;https://hskmanga.com/&quot;&gt;HSK Manga&lt;/a&gt;, which is free for anyone to use. It
    takes manga that already exists online and rewrites the dialogue just how a graded
    reader would. You pick an HSK level from 1 to 4, and every line in the chapter gets
    rewritten to that level.
  &lt;/p&gt;&lt;figure&gt;
    &lt;div&gt;
      &lt;img src=&quot;https://hskmanga.com/blog/img/hero-before-after.jpg&quot; alt=&quot;Before and after: a manga page turned into an HSK reader&quot; title=&quot;&quot;/&gt;
    &lt;/div&gt;
  &lt;/figure&gt;&lt;p&gt;
    I&amp;#39;ve been building HSK Manga for the past 6 months, as a junior in high school. In
    this post, I&amp;#39;ll go over the three most important stages in its pipeline.
  &lt;/p&gt;&lt;figure&gt;
    &lt;div&gt;
      &lt;img src=&quot;https://hskmanga.com/blog/img/pipeline-v2.png&quot; alt=&quot;The pipeline, with the three stages this post covers highlighted&quot; title=&quot;&quot;/&gt;
    &lt;/div&gt;
  &lt;/figure&gt;&lt;h2&gt;Extracting text&lt;/h2&gt;&lt;p&gt;
    Before I can simplify anything, I need the dialogue as actual text. Manga pages have
    text embedded into the images, so I&amp;#39;m forced to use optical character recognition
    (OCR), which is software that reads text out of an image. The one I use is
    PaddleOCR, and getting it to work on manga pages requires implementation beyond the
    base software.
  &lt;/p&gt;&lt;h3&gt;OCR issues&lt;/h3&gt;&lt;p&gt;
    If you run OCR on a manga page, it collects every single piece of text it can find,
    useful or not. Useless text is either background text or heavily stylized SFX (the
    sound-effect lettering drawn into the art). Capturing this text is bad because
    nobody reads it in the first place, and hand-drawn characters are really hard to
    render in a way that will match the art. I solved this by building a list of filters
    that use information from PaddleOCR (bounding-box dimensions, chroma, rotation, and
    confidence level) to drop text that shows clear signs of being unnecessary, highly
    stylized, or both. Getting these filters right took hundreds of sample chapters
    because of how hard it can be to differentiate between real dialogue and useless
    text.
  &lt;/p&gt;&lt;p&gt;
    Another issue with OCR is that it extracts text line by line, so a single speech
    bubble comes back as several fragments. This is bad because manga dialogue is split
    into speech bubbles, not lines. The solution is similar to how I filter out
    unnecessary text: I built a function that joins bounding boxes back together when
    certain requirements are met (roughly, when boxes are close and aligned enough to be
    the same bubble).
  &lt;/p&gt;&lt;figure&gt;
    &lt;div&gt;
      &lt;img src=&quot;https://hskmanga.com/blog/img/ocr-detection-v2.webp&quot; alt=&quot;OCR detections on a real page: five raw boxes, three after filtering, one after merging&quot; title=&quot;&quot;/&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      A real page from the sample chapter. OCR returns five boxes: three lines of
      dialogue and the two sound effects (喔耶, 啊啊). The sound effects get dropped and
      the three remaining lines get merged back into the single bubble they came from.
    &lt;/figcaption&gt;
  &lt;/figure&gt;&lt;p&gt;All of this lets me extract the exact pieces of text I want to simplify.&lt;/p&gt;&lt;h2&gt;Simplification&lt;/h2&gt;&lt;p&gt;
    When I started this project, I assumed getting the AI to simplify text would be
    really easy. All I had to do was write a great prompt, and the AI would do the rest.
    Through lots of testing, prompt changes, and some research into how others handled
    the same problem, I concluded that prompt engineering hits a ceiling on
    simplification quality. AI models are great at retaining meaning and grammar during
    simplification, but they lack a firm grasp of the different HSK levels, and they
    don&amp;#39;t simplify text enough.
  &lt;/p&gt;&lt;p&gt;
    To solve this, I tried creating a restriction where the model could only write words
    I allowed, using a grammar format called GBNF. In my case, that meant only low-level
    HSK words. But it was too strong of a constraint since there are many cases where a
    word does not have a simpler alternative. A common example of this would be a
    person&amp;#39;s name. This is why I moved to a softer inference-time setting called logit
    bias. Tokens are the sub-word chunks a model actually generates, and logit bias lets
    you nudge the model&amp;#39;s probability for specific tokens up or down. That let me make
    simpler words more likely and harder words less likely, without banning anything
    outright.
  &lt;/p&gt;&lt;figure&gt;
    &lt;div&gt;
      &lt;img src=&quot;https://hskmanga.com/blog/img/logit-bias-step.png&quot; alt=&quot;The model&amp;#39;s scores for every possible next word: on its own it picks the advanced word, and after the bias is applied it picks the beginner word instead&quot; title=&quot;&quot;/&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      The left bar graph shows a model&amp;#39;s base probabilities; the right shows them after
      logit bias is applied.
    &lt;/figcaption&gt;
  &lt;/figure&gt;&lt;p&gt;
    So how did I actually use logit bias? Two details matter here: the token bias map
    and the AI provider. To build the map, I scanned all the tokens in the model&amp;#39;s
    tokenizer vocabulary and sorted them into two groups: tokens whose characters all
    fall inside the target HSK level, and tokens that form words/phrases above that
    level. That second group matters because many advanced words are combinations of
    beginner characters. For the provider, I used Fireworks AI running DeepSeek V4
    Flash, the only place where there was no practical cap on a bias map this big
    (~22,000 tokens).
  &lt;/p&gt;&lt;p&gt;
    Once those two things were settled, I had to figure out how much bias to apply.
    After testing out many different values, I ended up with a ±2.5 bias value: a
    positive +2.5 on the in-level tokens to make them roughly 12 times more likely to
    get picked, and a negative −2.5 on the above-level tokens to make them less likely.
    I found that below 2.5, not enough words got simplified. Above it, the model started
    reaching for clumsy substitutions, turning 敏捷 (&amp;quot;agile&amp;quot;) into 快 (&amp;quot;fast&amp;quot;) and 沉浸
    (&amp;quot;immersed&amp;quot;) into 喜欢 (&amp;quot;like&amp;quot;) in places where the harder word was the right one.
  &lt;/p&gt;&lt;p&gt;
    Now that the dialogue is simplified enough to read, it needs to be placed onto the
    images.
  &lt;/p&gt;&lt;h2&gt;Erasing, covering, and rendering new text&lt;/h2&gt;&lt;p&gt;
    For the output chapter to actually look nice, the original text needs to be either
    covered or erased.
  &lt;/p&gt;&lt;figure&gt;
    &lt;div&gt;
      &lt;img src=&quot;https://hskmanga.com/blog/img/erase-before-after.webp&quot; alt=&quot;The same page shown three ways: original, with the dialogue erased, and with the simplified text rendered back in&quot; title=&quot;&quot;/&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      The same page from the OCR section, taken through the full swap. The sound effects
      survive because the filters dropped them earlier, so they were never marked for
      removal.
    &lt;/figcaption&gt;
  &lt;/figure&gt;&lt;p&gt;
    To do this, I use the bounding boxes from my OCR to select the areas where I&amp;#39;ll
    either erase the text or cover it. Text is erased with LaMa when the background
    isn&amp;#39;t a solid color, and it&amp;#39;s covered when the background is solid. LaMa is an AI
    inpainting model that fills an erased region with plausible background art, similar
    to Photoshop&amp;#39;s content-aware fill.
  &lt;/p&gt;&lt;p&gt;
    Determining whether the background is solid is as simple as sampling a small set of
    pixels and checking what fraction of them match the median color.
  &lt;/p&gt;&lt;figure&gt;
    &lt;div&gt;
      &lt;img src=&quot;https://hskmanga.com/blog/img/ring-sampling.webp&quot; alt=&quot;Sampling the strips beside a text box to decide whether to cover it or erase it&quot; title=&quot;&quot;/&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      Anything that hits 95% or higher gets covered with a solid color. The speech
      bubble on the left is a clean 100%. The text sitting on artwork on the right only
      reaches 49%, so it goes to LaMa instead.
    &lt;/figcaption&gt;
  &lt;/figure&gt;&lt;p&gt;
    On a solid background, covering and LaMa create the same visual result, but covering
    the text is much more efficient. For text placed on top of artwork, LaMa looks far
    better, since it can predict what the background should be.
  &lt;/p&gt;&lt;figure&gt;
    &lt;div&gt;
      &lt;img src=&quot;https://hskmanga.com/blog/img/cover-vs-lama.webp&quot; alt=&quot;The same text region covered with a solid color and erased with LaMa&quot; title=&quot;&quot;/&gt;
    &lt;/div&gt;
  &lt;/figure&gt;&lt;p&gt;
    Once the original text is either covered or erased, new text needs to be generated.
    This step is as simple as taking the bounding box from the OCR and using it like a
    text box for the simplified text.
  &lt;/p&gt;&lt;h2&gt;Where it stands&lt;/h2&gt;&lt;p&gt;
    As of today, HSK Manga has simplified over 500 chapters. In this blog post I only
    mentioned the general functionality of the pipeline, but there are many other
    features available such as pinyin, definitions, and audio. My project still isn&amp;#39;t
    finished though. Comments, emails, or the form at
    &lt;a href=&quot;https://hskmanga.com/feedback&quot;&gt;hskmanga.com/feedback&lt;/a&gt; are highly appreciated. I read
    everything! I&amp;#39;m always looking to improve HSK Manga, whether that&amp;#39;s simplification
    quality, more features, or a better interface.
  &lt;/p&gt;</content:encoded>
</item>
<item>
<title>Building a decentralized bulletin board (and accidentally reinventing Nostr)</title>
<link>http://en.andros.dev/blog/4c4e1b40/building-a-decentralized-bulletin-board-and-accidentally-reinventing-nostr/</link>
<guid isPermaLink="false">If8cU-7EJ-inYAIkvhckdFCR85gqjBQ305PsEg==</guid>
<pubDate>Sun, 30 Aug 2026 17:03:31 +0000</pubDate>
<description>Let&#39;s do a software engineering exercise. Our goal is to build a bulletin board. A place where anyone can post an ad, a note, a message, with no account and no sign-up. Some old hands already know I mean a BBS without registration, others will picture something like X or Mastodon. It sits somewhere in between. Let&#39;s carry on. We add a new requirement: I don&#39;t want it to depend on a single server. Each person can run their own instance to publish, or publish on someone else&#39;s instance. It make...</description>
<content:encoded>&lt;p&gt;Let&amp;#39;s do a software engineering exercise. Our goal is to build a bulletin board. A place where anyone can post an ad, a note, a message, with no account and no sign-up. Some old hands already know I mean a BBS without registration, others will picture something like X or Mastodon. It sits somewhere in between. Let&amp;#39;s carry on.&lt;/p&gt;&lt;p&gt;We add a new requirement: I don&amp;#39;t want it to depend on a single server. Each person can run their own instance to publish, or publish on someone else&amp;#39;s instance. It makes no difference to the end result. Every reader, no matter which server they check, will end up seeing the same notes in the same order. We just opened the box of decentralization.&lt;/p&gt;&lt;p&gt;Now the servers have to coordinate. If every board is a copy of the whole list, and anyone can write on any board, theirs or someone else&amp;#39;s, how on earth do they all agree on which ads exist? That is the heart of this article.&lt;/p&gt;&lt;p&gt;Let me give you a spoiler: by the time we finish, we will have accidentally reinvented two technologies that already existed: FidoNet (1984) and Nostr, with its negentropy sync (2023). But let&amp;#39;s not get ahead of ourselves.&lt;/p&gt;&lt;p&gt;Let&amp;#39;s set some limits to narrow the problem down and learn what interests us.&lt;/p&gt;&lt;h2&gt;The rules of the game&lt;/h2&gt;&lt;p&gt;The features that define our board:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;There will be a single board shared across all servers: no topics, no sections.&lt;/li&gt;&lt;li&gt;Each node keeps a full copy of the board.&lt;/li&gt;&lt;li&gt;Everything is public: no login, no sign-up, no passwords, no authentication. You show up, you write, you pin your ad, done.&lt;/li&gt;&lt;li&gt;A reply is just another ad: if Bob wants to answer Ana&amp;#39;s ad, he posts a new ad that points to hers. Some hang off others, but deep down they are the same thing.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So a board is a &lt;strong&gt;list of ads&lt;/strong&gt;. And syncing two nodes means getting both to hold the same list.&lt;/p&gt;&lt;p&gt;In maths that is called a set, and agreeing on a set is called &lt;em&gt;reconciliation&lt;/em&gt;. Remember the terms, because I&amp;#39;ll keep using them.&lt;/p&gt;&lt;h2&gt;First attempt: send me everything&lt;/h2&gt;&lt;p&gt;Ana brings up her node. Bob brings up his. Each has their own ads. Let&amp;#39;s sync them.&lt;/p&gt;&lt;p&gt;The obvious approach would be this: Ana sends Bob her entire list of ads, Bob compares it with his own and keeps the ones he was missing. Then the other way around. And that&amp;#39;s it. Solved. Believe it or not, this works. It is the simplest way to reconcile sets: exchange everything and drop the duplicates. However, it is extremely inefficient.&lt;/p&gt;&lt;p&gt;Imagine the two of them have 1,000,000 ads and differ in only 3. With this method, Ana sends a million lines to find out that Bob already had 999,997 of them right. You moved an entire house to discover only three bricks were missing.&lt;/p&gt;&lt;p&gt;Now picture doing this every few minutes to stay up to date. A million lines every time! It is unsustainable, slow and expensive.&lt;/p&gt;&lt;p&gt;Let&amp;#39;s change strategy: the cost of syncing should be proportional to &lt;strong&gt;how much the two boards differ&lt;/strong&gt;, not to how big they are. In other words, if both nodes have 1,000,000 ads and differ in only 3, then only 3 ads should move.&lt;/p&gt;&lt;p&gt;But first we need to solve a previous problem.&lt;/p&gt;&lt;h2&gt;How do we know we already have the same ad?&lt;/h2&gt;&lt;p&gt;When Ana and Bob compare their ads, how do they know if two of them are the same? The first thing that comes to mind is to compare letter by letter. But that is fragile. A single extra space, a different line break, and the ad looks like another one.&lt;/p&gt;&lt;p&gt;The solution is to give each ad an identifier that comes from its own content. You run the text through a hash function (a fixed-length summary, like SHA-256) and that summary &lt;strong&gt;is&lt;/strong&gt; its identifier.&lt;/p&gt;&lt;p&gt;For example, the ad:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;ad: &amp;quot;Selling bike, reach me at Ana&amp;#39;s node&amp;quot; id: 3f9a...c17&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can compute it with a simple terminal command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo -n &amp;quot;Selling bike, reach me at Ana&amp;#39;s node&amp;quot; | sha256sum&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is the good part. Two ads with the same content have the same identifier, always, on any node, without coordinating. And two different ads have different identifiers. The identifier is assigned by the content, not by some central authoritative node or anything like that.&lt;/p&gt;&lt;p&gt;This is called &lt;em&gt;content addressing&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;And that&amp;#39;s not all. It has other useful properties:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Duplicate detection&lt;/strong&gt;: if the same ad reaches Bob twice, along two different paths, both carry the same &lt;code&gt;id&lt;/code&gt;. He keeps one and drops the other. Without thinking.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;An order&lt;/strong&gt;: a hash is really just a huge number. That &lt;code&gt;3f9a...c17&lt;/code&gt; is in hexadecimal, but underneath it is a figure, so the &lt;code&gt;id&lt;/code&gt;s can be sorted from smallest to largest.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And a sorted set will come in very handy for reconciliation.&lt;/p&gt;&lt;h2&gt;Telling the neighbors&lt;/h2&gt;&lt;p&gt;Let&amp;#39;s go back to syncing, but thinking in terms of a network, not two lone nodes.&lt;/p&gt;&lt;p&gt;When someone pins an ad on Ana&amp;#39;s node, the natural thing is for Ana to pass it to the nodes she knows. Those pass it to theirs. And so the ad spreads across the whole network, hopping from neighbor to neighbor, like a rumor. This technique is called &lt;em&gt;flood-fill&lt;/em&gt;, flooding. Many peer-to-peer networks use it to spread information with no central node, but it has a design flaw: if the network grows a lot, the information can get stuck in a loop.&lt;/p&gt;&lt;p&gt;For example, Ana tells Bob, Bob tells Carla, Carla tells Ana again... and the ad goes around forever.&lt;/p&gt;&lt;p&gt;Here content addressing already saves us halfway: since the &lt;code&gt;id&lt;/code&gt; is the same, when the ad comes back to Ana she recognizes it and does not resend it. But we can do better and not send things we know the other one already has.&lt;/p&gt;&lt;p&gt;The classic trick is to write down, next to each ad, which nodes it has already passed through. A &amp;quot;seen by&amp;quot; list. Before sending an ad to Carla, I check whether Carla is already in its &amp;quot;seen by&amp;quot; list. If she is, I save myself the trouble. It is exactly what FidoNet did in the eighties with its &lt;code&gt;SEEN-BY&lt;/code&gt; field, and Usenet with the &lt;code&gt;Path&lt;/code&gt; and &lt;code&gt;Message-ID&lt;/code&gt; headers.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;FidoNet is a network of BBSes from the 80s that synced in the middle of the night, when phone calls were cheap.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Another technique, complementary or alternative, is to have a hop limit. If an ad has passed through N nodes, I stop resending it. For example, the Meshtastic network uses a hop limit, recommended at 3, together with the &amp;quot;seen by&amp;quot; list.&lt;/p&gt;&lt;p&gt;Even so, flooding takes for granted something that is not realistic: that all nodes are connected and listening at the exact moment the ad goes by.&lt;/p&gt;&lt;h2&gt;If you weren&amp;#39;t there, you miss it&lt;/h2&gt;&lt;p&gt;Flooding has a big flaw: it only works if you are listening at the right moment. What if your node was off when the rumor went by? Then you never find out. The ad passed by and nobody is going to offer it to you again.&lt;/p&gt;&lt;p&gt;We need something more robust. Instead of living in the stream, we need a reconciliation mechanism: when two nodes meet, they compare what they have and catch up. Whenever that happens, no matter how old their ads are, even if you have been offline for a month.&lt;/p&gt;&lt;p&gt;We are back to the first problem: how do we compare without sending each other the whole board?&lt;/p&gt;&lt;h2&gt;Comparing summaries: the Merkle tree&lt;/h2&gt;&lt;p&gt;Let&amp;#39;s bring back the hash function that told us whether two ads are equal.&lt;/p&gt;&lt;p&gt;Instead of sending the ads, Ana and Bob send a &lt;strong&gt;summary&lt;/strong&gt; of their ads. A single hash that represents &lt;em&gt;all&lt;/em&gt; of their board. If both summaries match, they have exactly the same list. Done. That way only a single message is sent, one that says: &amp;quot;we have the same thing, nothing to do&amp;quot;.&lt;/p&gt;&lt;p&gt;And if they don&amp;#39;t match... the rock and roll begins.&lt;/p&gt;&lt;p&gt;The classic way to look without checking everything is a &lt;em&gt;Merkle tree&lt;/em&gt;. You take your ads, group them, hash each group, then hash the hashes, and so on until you reach a single hash at the very top: the root. You compare roots. If they differ, you go down the branches whose hashes don&amp;#39;t match, and ignore the ones that do. You corner the difference.&lt;/p&gt;&lt;p&gt;For example, suppose Ana and Bob have these ads:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;Ana: {3, 8, 15, 16, 23, 42, 55, 60} Bob: {3, 8, 15, 16, 23, 42, 55, 99}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We group them in pairs, hash each group, then hash those hashes, and so on up to the root:&lt;/p&gt;&lt;pre&gt;flowchart TD Root[&amp;quot;ROOT = hash(Left, Right)&amp;quot;] --&amp;gt; L[&amp;quot;Left = hash(A, B)&amp;quot;] Root --&amp;gt; R[&amp;quot;Right = hash(C, D)&amp;quot;] L --&amp;gt; A[&amp;quot;A = hash(3, 8)&amp;quot;] L --&amp;gt; B[&amp;quot;B = hash(15, 16)&amp;quot;] R --&amp;gt; C[&amp;quot;C = hash(23, 42)&amp;quot;] R --&amp;gt; D[&amp;quot;D = hash(55, 60)&amp;quot;]&lt;/pre&gt;&lt;p&gt;Ana and Bob have the same tree except at leaf D: Ana holds 60 and Bob holds 99. They agree like this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;They compare roots. Different, there is a difference somewhere.&lt;/li&gt;&lt;li&gt;They go down a level. The Left branch matches on both, so they prune it whole: half the list discarded in one shot.&lt;/li&gt;&lt;li&gt;The Right branch does not match. They keep going down. Inside, C matches and D does not.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In four comparisons they have cornered the difference down to group D, without looking at the rest of the board. Exactly what we wanted: compare summaries and go down only where things don&amp;#39;t add up.&lt;/p&gt;&lt;p&gt;Fun fact for the nerds: this is what Cassandra or Amazon&amp;#39;s Dynamo use to keep their replicas in check.&lt;/p&gt;&lt;p&gt;It is almost perfect. It just has 2 limitations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The tree has a fixed shape. To compare level with level, both nodes must have chopped up their ads exactly the same way, into the same groups. You don&amp;#39;t decide on the fly where to look, the structure is decided in advance.&lt;/li&gt;&lt;li&gt;The tree&amp;#39;s leaves are fixed-size buckets. If a bucket doesn&amp;#39;t match, they send you the whole bucket, even if only one ad inside it changed. The tree takes you to the box, not to the ad.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These are subtle limitations, but they can turn huge under heavy traffic.&lt;/p&gt;&lt;p&gt;Luckily, 2023 brought us a solution.&lt;/p&gt;&lt;h2&gt;The evolution: range-based reconciliation&lt;/h2&gt;&lt;p&gt;The idea is called &lt;em&gt;range-based set reconciliation&lt;/em&gt;. You&amp;#39;ll be surprised how simple and elegant it is.&lt;/p&gt;&lt;p&gt;To start with, there is no tree. It is 2 ideas working together: fingerprints and binary search.&lt;/p&gt;&lt;p&gt;First, a &lt;strong&gt;fingerprint&lt;/strong&gt; is a hash that summarizes all the ads in a range. A range is a stretch of the sorted list, for example &amp;quot;all ads whose &lt;code&gt;id&lt;/code&gt; runs from 3 to 60&amp;quot;. If the fingerprint of a range matches on both nodes, that stretch is synced and there is no need to look inside.&lt;/p&gt;&lt;p&gt;The simplest way to compute the fingerprint is to take the &lt;code&gt;id&lt;/code&gt;s of all the ads in the range and combine them with an operation that does not depend on order, like adding them up or XOR-ing them.&lt;/p&gt;&lt;p&gt;That has two advantages:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Two nodes with the same ads get the same fingerprint even if they store them in a different order.&lt;/li&gt;&lt;li&gt;It can be recomposed in chunks: the fingerprint of a big range comes from joining those of its halves, without recomputing anything.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In a real system you use something more robust than a sum, so that nobody can craft two different sets with the same fingerprint, but the idea is exactly that.&lt;/p&gt;&lt;p&gt;And second: if the fingerprints of a range don&amp;#39;t match, &lt;strong&gt;I split the range in half&lt;/strong&gt; and repeat on each half.&lt;/p&gt;&lt;p&gt;That&amp;#39;s it. It is a binary search over the difference.&lt;/p&gt;&lt;p&gt;The whole algorithm fits in a handful of lines:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def reconcile(range): if my_fingerprint(range) == other_fingerprint(range): return # equal: nothing to do if few_items(range): exchange_ads(range) # cheap now: just send them return left, right = split(range) # split the range in half reconcile(left) # and repeat on each part reconcile(right)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Three cases and no more. If the fingerprints match, you stay quiet. If the range is already small, you send the ads directly. And if not, you split in half and go down each side. The recursion switches itself off on the branches that match and only digs where there are real differences.&lt;/p&gt;&lt;p&gt;Let&amp;#39;s go with an example.&lt;/p&gt;&lt;p&gt;Remember that an ad&amp;#39;s &lt;code&gt;id&lt;/code&gt; is a hash, that is, a huge number, so sorting the ads by their &lt;code&gt;id&lt;/code&gt; makes total sense. Here I draw them as small numbers so it reads easily. Ana&amp;#39;s board and Bob&amp;#39;s are almost identical, they only differ in the last one:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;Ana: {3, 8, 15, 16, 23, 42, 55, 60} Bob: {3, 8, 15, 16, 23, 42, 55, 99}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Watch how they agree:&lt;/p&gt;&lt;pre&gt;flowchart TD R[&amp;quot;Whole board&lt;br/&gt;Ana&amp;#39;s fingerprint ≠ Bob&amp;#39;s fingerprint&amp;quot;] --&amp;gt; L[&amp;quot;Left half: {3,8,15,16}&lt;br/&gt;fingerprints MATCH, we stop here&amp;quot;] R --&amp;gt; D[&amp;quot;Right half: {23,42,55,...}&lt;br/&gt;fingerprints differ, we keep going&amp;quot;] D --&amp;gt; DL[&amp;quot;{23,42}&lt;br/&gt;fingerprints MATCH, we stop&amp;quot;] D --&amp;gt; DR[&amp;quot;{55, last}&lt;br/&gt;fingerprints differ, we keep going&amp;quot;] DR --&amp;gt; DRL[&amp;quot;{55}&lt;br/&gt;MATCH&amp;quot;] DR --&amp;gt; DRR[&amp;quot;{60} on Ana vs {99} on Bob&lt;br/&gt;tiny range: the ads are exchanged&amp;quot;]&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;Ana sends &lt;strong&gt;a single fingerprint&lt;/strong&gt; of her whole board. Bob compares it with his. They don&amp;#39;t match, there is a difference.&lt;/li&gt;&lt;li&gt;They split in half. The left half, &lt;code&gt;{3,8,15,16}&lt;/code&gt;, gives the same fingerprint on both. &lt;strong&gt;With a single comparison, half the list is discarded.&lt;/strong&gt; Nothing more is looked at there.&lt;/li&gt;&lt;li&gt;The right half doesn&amp;#39;t match. It is split again. &lt;code&gt;{23,42}&lt;/code&gt; matches, out. The other chunk doesn&amp;#39;t.&lt;/li&gt;&lt;li&gt;It keeps splitting until the range is so small that summarizing no longer pays off. Ana sends &lt;code&gt;60&lt;/code&gt;, Bob sends &lt;code&gt;99&lt;/code&gt;, and the two converge.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Count the messages, it&amp;#39;s a handful of fingerprints and two ads! Instead of the eight lines of the dumb method.&lt;/p&gt;&lt;p&gt;With eight elements it is not impressive. But give it a million ads that differ in a single one. Range-based reconciliation finds it in about twenty comparisons, the logarithm of the size, instead of sending a million lines.&lt;/p&gt;&lt;p&gt;This holds as long as the differences are few. The more there are, the more branches you have to open. But you never pay for what you already have in common. It is very efficient.&lt;/p&gt;&lt;p&gt;Traffic stops depending on how big the board is and starts depending only on &lt;strong&gt;how much the two of them differ&lt;/strong&gt;. We have already solved the syncing problem.&lt;/p&gt;&lt;p&gt;And look at everything we have gained over the Merkle tree:&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Merkle tree&lt;/th&gt;&lt;th&gt;Range-based reconciliation&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Partition&lt;/td&gt;&lt;td&gt;Fixed, identical on both nodes&lt;/td&gt;&lt;td&gt;On the fly, where the differences are&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Summaries&lt;/td&gt;&lt;td&gt;The tree, down to a certain depth&lt;/td&gt;&lt;td&gt;On the fly, only in the ranges you visit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;What it ends up transferring&lt;/td&gt;&lt;td&gt;The whole bucket that doesn&amp;#39;t match&lt;/td&gt;&lt;td&gt;The exact ad that differs&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;What they must share&lt;/td&gt;&lt;td&gt;The shape of the tree&lt;/td&gt;&lt;td&gt;Only the order of the elements&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;It is true that there are modern Merkle trees that dodge part of this: &lt;em&gt;Merkle Search Trees&lt;/em&gt; and &lt;em&gt;Prolly Trees&lt;/em&gt;. Their shape is derived from the content, so two replicas with the same ads reach the same tree no matter what order they received them in. They are great, and Bluesky actually uses them for its repositories. Still, they remain a tree you have to build and load. It is a design problem you can only ease, not remove.&lt;/p&gt;&lt;p&gt;For the record, reconciling sets over a network was not invented yesterday, there is work going back to the 2000s (Minsky and company). What the range-based approach that Aljoscha Meyer formalized (2022-2023) brings is a generic version, practical and easy to implement. And it is not chalkboard theory: the &lt;a href=&quot;https://willowprotocol.org/&quot;&gt;Willow protocol&lt;/a&gt; uses it to sync decentralized data, and &lt;a href=&quot;https://www.iroh.computer/&quot;&gt;Iroh&lt;/a&gt; brings it to Rust in its documents layer, iroh-docs, to sync devices directly. It is some of the most interesting work being built in syncing today.&lt;/p&gt;&lt;h2&gt;Wait a second: I just reinvented FidoNet&lt;/h2&gt;&lt;p&gt;Look back at what we have built. Many equal nodes. Each with its copy of the board. Ads identified by their content so they don&amp;#39;t duplicate. A &amp;quot;seen by&amp;quot; field to avoid sending the same thing twice. Syncing every now and then to catch up.&lt;/p&gt;&lt;p&gt;That is FidoNet, literally. The BBS network that Tom Jennings started in 1984 did exactly this. They solved the decentralized board problem forty years ago, with modems and in the dark.&lt;/p&gt;&lt;p&gt;We have not invented anything new. We have polished something that was already good.&lt;/p&gt;&lt;h2&gt;Prototyping the endpoints&lt;/h2&gt;&lt;p&gt;Without writing the code, this is what the API would look like.&lt;/p&gt;&lt;p&gt;For people, to publish and read:&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Route&lt;/th&gt;&lt;th&gt;What it does&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/ads&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Publish: you send the text, the node returns its &lt;code&gt;id&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/ads&lt;/code&gt;&lt;/td&gt;&lt;td&gt;List the board&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/ads/{id}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Read a single ad&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;Publishing is sending the text and getting back the &lt;code&gt;id&lt;/code&gt; the node computed:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;POST /ads Content-Type: text/plain Selling bike, reach me at Ana&amp;#39;s node&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &amp;quot;id&amp;quot;: &amp;quot;3f9a...c17&amp;quot; }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And reading an ad returns its text along with the &lt;code&gt;id&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;GET /ads/3f9a...c17&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &amp;quot;id&amp;quot;: &amp;quot;3f9a...c17&amp;quot;, &amp;quot;text&amp;quot;: &amp;quot;Selling bike, reach me at Ana&amp;#39;s node&amp;quot; }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Between nodes, to sync:&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Route&lt;/th&gt;&lt;th&gt;What it does&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/reconcile&lt;/code&gt;&lt;/td&gt;&lt;td&gt;The core: you pass it a range and its fingerprint, and it answers whether it matches, whether it splits it, or whether it sends you its &lt;code&gt;id&lt;/code&gt;s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/ads/fetch&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Ask for the bodies of the &lt;code&gt;id&lt;/code&gt;s that reconciliation revealed are missing&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;To &lt;code&gt;/reconcile&lt;/code&gt; you pass a range and its fingerprint:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &amp;quot;range&amp;quot;: { &amp;quot;from&amp;quot;: &amp;quot;0000&amp;quot;, &amp;quot;to&amp;quot;: &amp;quot;ffff&amp;quot; }, &amp;quot;fingerprint&amp;quot;: &amp;quot;a3f1b2...&amp;quot; }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And it answers with one of the three cases from the pseudocode:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// 1. Match: nothing to do in this stretch { &amp;quot;status&amp;quot;: &amp;quot;equal&amp;quot; } // 2. Small range: here are my ids, compare them yourself { &amp;quot;status&amp;quot;: &amp;quot;ids&amp;quot;, &amp;quot;ids&amp;quot;: [&amp;quot;3f9a...c17&amp;quot;, &amp;quot;8b21...0d4&amp;quot;] } // 3. They differ: I split it and give you each half&amp;#39;s fingerprint { &amp;quot;status&amp;quot;: &amp;quot;partition&amp;quot;, &amp;quot;subranges&amp;quot;: [ { &amp;quot;from&amp;quot;: &amp;quot;0000&amp;quot;, &amp;quot;to&amp;quot;: &amp;quot;8000&amp;quot;, &amp;quot;fingerprint&amp;quot;: &amp;quot;11aa...&amp;quot; }, { &amp;quot;from&amp;quot;: &amp;quot;8000&amp;quot;, &amp;quot;to&amp;quot;: &amp;quot;ffff&amp;quot;, &amp;quot;fingerprint&amp;quot;: &amp;quot;cc44...&amp;quot; } ]}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When reconciliation reveals which &lt;code&gt;id&lt;/code&gt;s you are missing, you ask for their texts with &lt;code&gt;/ads/fetch&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;POST /ads/fetch Content-Type: application/json { &amp;quot;ids&amp;quot;: [&amp;quot;8b21...0d4&amp;quot;] }&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[ { &amp;quot;id&amp;quot;: &amp;quot;8b21...0d4&amp;quot;, &amp;quot;text&amp;quot;: &amp;quot;Looking for a flat downtown&amp;quot; } ]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The node that syncs calls &lt;code&gt;/reconcile&lt;/code&gt; in a loop, going down the subranges that differ (the three cases from the pseudocode above), and at the end asks with &lt;code&gt;/ads/fetch&lt;/code&gt; only for the ads it didn&amp;#39;t have. The hashes travel many times; the full text, only once and at the end.&lt;/p&gt;&lt;p&gt;A reply needs no route of its own: it is another &lt;code&gt;POST /ads&lt;/code&gt; whose text carries inside it the &lt;code&gt;id&lt;/code&gt; of the ad it answers.&lt;/p&gt;&lt;h2&gt;This is Nostr, piece by piece&lt;/h2&gt;&lt;p&gt;Without meaning to, we have almost built &lt;strong&gt;Nostr&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Every Nostr event is identified by the &lt;strong&gt;SHA-256 hash of its content&lt;/strong&gt;. That is our content &lt;code&gt;id&lt;/code&gt;, exactly.&lt;/li&gt;&lt;li&gt;It is published on &lt;strong&gt;relays&lt;/strong&gt;: open servers where you write without signing up, just with a key pair. That is our HTTP endpoint with no accounts.&lt;/li&gt;&lt;li&gt;And to sync, the relays speak &lt;strong&gt;negentropy&lt;/strong&gt;, which is nothing other than range-based reconciliation. The very same one we just derived by hand. Doug Hoyte wrote it for the &lt;code&gt;strfry&lt;/code&gt; relay, and it lives in the spec as NIP-77.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So our BBS is practically Nostr. The one deep difference is that Nostr events are &lt;strong&gt;signed&lt;/strong&gt; with the author&amp;#39;s key, so there is an identity behind them. We dropped it to keep things simple.&lt;/p&gt;&lt;p&gt;Now we understand what is under the hood of Nostr and why it works.&lt;/p&gt;&lt;h2&gt;The problems we haven&amp;#39;t solved&lt;/h2&gt;&lt;p&gt;Our BBS works, but it has holes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;There is no identity, so there are no culprits.&lt;/strong&gt; We dropped login and sign-up to keep it simple, and a network where publishing is free and anonymous is a network that fills up with spam within hours.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;We can&amp;#39;t moderate without authority.&lt;/strong&gt; On a single server, the admin deletes and that&amp;#39;s that. Here there is no admin. If Ana flags an ad as garbage, why would Bob&amp;#39;s node listen to her? And what if the one flagging garbage is the spammer, to bury everyone else? A mess.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Without identity, voting is useless.&lt;/strong&gt; The temptation is to let people flag spam and have an algorithm sink it. But with no accounts, I fabricate a thousand fake identities and vote whatever I like. It is the famous &lt;em&gt;Sybil attack&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;It grows forever.&lt;/strong&gt; If nobody can really delete, and everything is replicated on every node, the board only gets fatter.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;None of these problems is about syncing, they are about &lt;strong&gt;trust&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Every decentralized system fights with this, and each one solves it in its own way.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Mastodon and the Fediverse&lt;/strong&gt; trust the admin of each instance. Each server moderates its own: it deletes, suspends users and can &lt;em&gt;defederate&lt;/em&gt;, that is, block another whole instance. Shared block lists circulate among admins. You pick an instance and inherit its judgment. It works, but moderation ends up fragmented, and moving instances is costly.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Bluesky&lt;/strong&gt; flips it around with &lt;em&gt;composable labels&lt;/em&gt;. Anyone can run a service that labels accounts and posts (&amp;quot;spam&amp;quot;, &amp;quot;nsfw&amp;quot;), and each user subscribes to the labelers they trust. Your client hides or warns based on those labels. Moderation stops being a single truth and becomes a service you sign up for.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The reputation route&lt;/strong&gt;, the closest to our account-less BBS, weights each vote by how much those who are already trusted trust you (EigenTrust-style algorithms). A freshly fabricated fake identity has nobody vouching for it, so its vote carries no weight. That is the trick to dodge the Sybil attack fairly well.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It is funny, because Bluesky does the same thing Usenet already did in the nineties with NoCeM: signed notices saying &amp;quot;this is spam&amp;quot; that each reader chose whether to apply or not.&lt;/p&gt;&lt;h2&gt;Final notes&lt;/h2&gt;&lt;p&gt;Syncing, which looked like the hard part, turned out to be a closed and even elegant problem. What has beaten us is trust: who you are, whether I believe you, how I kick out a spammer with no boss to press the button. Syncing is a maths problem. Trust is a political one.&lt;/p&gt;&lt;p&gt;So next time you hear about Nostr, about Willow, about Iroh or about &amp;quot;local-first&amp;quot;, you won&amp;#39;t see magic anymore. You&amp;#39;ll see a bulletin board with an &lt;code&gt;id&lt;/code&gt; that is a hash and two nodes comparing fingerprints. And that is the beautiful part of deriving something from scratch.&lt;/p&gt;&lt;h2&gt;Sources&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://arxiv.org/pdf/2212.13567&quot;&gt;Range-Based Set Reconciliation&lt;/a&gt;, by Aljoscha Meyer. The work that generalizes and analyzes the technique. Dense, but it is the source.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://willowprotocol.org/specs/rbsr/index.html&quot;&gt;3d Range-Based Set Reconciliation&lt;/a&gt; in the Willow protocol specification. The applied version, with fingerprints and recursive partitioning.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://logperiodic.com/rbsr.html&quot;&gt;Range-Based Set Reconciliation&lt;/a&gt; at Log Periodic, a more digestible explanation of the same idea.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.iroh.computer/&quot;&gt;Iroh&lt;/a&gt; and Iroh-Docs, the Rust implementation that brings range-based reconciliation to real devices.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/nostr-protocol/nips/blob/master/77.md&quot;&gt;NIP-77: Negentropy Syncing&lt;/a&gt;, range-based reconciliation as Nostr uses it, with Doug Hoyte&amp;#39;s &lt;a href=&quot;https://github.com/hoytech/strfry&quot;&gt;strfry&lt;/a&gt; relay as the reference implementation.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://earthstar-project.org/&quot;&gt;Earthstar&lt;/a&gt;, a decentralized database with no traditional accounts that syncs by ranges, today on top of Willow.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;http://ftsc.org/docs/fts-0004.001&quot;&gt;FTS-0004 EchoMail Specification&lt;/a&gt; from the FTSC, where FidoNet&amp;#39;s &lt;code&gt;MSGID&lt;/code&gt; and &lt;code&gt;SEEN-BY&lt;/code&gt; live.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/FidoNet&quot;&gt;FidoNet&lt;/a&gt; on Wikipedia, for the history of Tom Jennings&amp;#39; BBS network and its nightly sync model.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/activitypub/&quot;&gt;ActivityPub&lt;/a&gt;, the W3C recommendation. The inbox-delivery model that dispatches instead of reconciling.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Merkle_tree&quot;&gt;Merkle tree&lt;/a&gt; and Apache Cassandra&amp;#39;s &lt;a href=&quot;https://cassandra.apache.org/doc/latest/cassandra/managing/operating/repair.html&quot;&gt;anti-entropy&lt;/a&gt;, for the step before range-based reconciliation.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://bsky.social/about/blog/4-13-2023-moderation&quot;&gt;Composable Moderation&lt;/a&gt; on the Bluesky blog, the model of labelers you subscribe to.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://nlp.stanford.edu/pubs/eigentrust.pdf&quot;&gt;The EigenTrust Algorithm for Reputation Management in P2P Networks&lt;/a&gt;, the transitive reputation that isolates the Sybil attack.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/NoCeM&quot;&gt;NoCeM&lt;/a&gt;, the signed spam notices on Usenet in the nineties, ancestor of composable labels.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/2color/awesome-prolly-tree&quot;&gt;Prolly Trees&lt;/a&gt; and Merkle Search Trees, variants of the Merkle tree whose shape is derived from the content and not from the insertion order.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.joinmastodon.org/2025/10/mastodon-4-5-for-devs/&quot;&gt;Mastodon 4.5 for Developers&lt;/a&gt;, which enables fetching a thread&amp;#39;s missing replies by default.&lt;/li&gt;&lt;/ul&gt;&lt;hr/&gt;&lt;p&gt;Help me keep writing Every coffee gives me a push toward the next article. &lt;a href=&quot;https://ko-fi.com/W7W02LB83&quot;&gt;Sure, it&amp;#39;s on me!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Send an email to &lt;a href=&quot;mailto:comment+article-4c4e1b40@andros.dev&quot;&gt;comment+article-4c4e1b40@andros.dev&lt;/a&gt; to leave a comment. The subject will be ignored.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>htmx and Django LiveView, side by side</title>
<link>http://en.andros.dev/blog/94d14a9e/htmx-and-django-liveview-side-by-side/</link>
<guid isPermaLink="false">oug7Faxa55vhe1bNNOMfuiO1AmFHS9pGMxE-BA==</guid>
<pubDate>Sun, 30 Aug 2026 17:03:31 +0000</pubDate>
<description>If you use htmx and are considering migrating to Django LiveView, this article is for you. It&#39;s not an exhaustive comparison, but a run-through of the most common cases and how each technology solves them. Don&#39;t read it as a LiveView tutorial, since it leaves many features out. I&#39;ll assume you know htmx and Django, and that you know how to create views, templates and routes. If not, I recommend reading the documentation for each one before continuing. Fundamental differences Before looking at...</description>
<content:encoded>&lt;p&gt;If you use &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; and are considering migrating to &lt;a href=&quot;https://django-liveview.andros.dev&quot;&gt;Django LiveView&lt;/a&gt;, this article is for you. It&amp;#39;s not an exhaustive comparison, but a run-through of the most common cases and how each technology solves them. Don&amp;#39;t read it as a LiveView tutorial, since it leaves many features out.&lt;/p&gt;&lt;p&gt;I&amp;#39;ll assume you know htmx and Django, and that you know how to create views, templates and routes. If not, I recommend reading the documentation for each one before continuing.&lt;/p&gt;&lt;h2&gt;Fundamental differences&lt;/h2&gt;&lt;p&gt;Before looking at code, let&amp;#39;s be clear: architecturally they are very different. They don&amp;#39;t even use the same communication protocol.&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;htmx&lt;/th&gt;&lt;th&gt;Django LiveView&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Protocol&lt;/td&gt;&lt;td&gt;HTTP/AJAX&lt;/td&gt;&lt;td&gt;WebSockets&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Communication&lt;/td&gt;&lt;td&gt;Individual requests (GET, POST, etc.)&lt;/td&gt;&lt;td&gt;Persistent connection&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;State&lt;/td&gt;&lt;td&gt;Stateless&lt;/td&gt;&lt;td&gt;Stateful&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Real-time updates&lt;/td&gt;&lt;td&gt;Partial, with polling&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Infrastructure requirements&lt;/td&gt;&lt;td&gt;Minimal (HTTP server)&lt;/td&gt;&lt;td&gt;Moderate (Channels + Redis recommended)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency&lt;/td&gt;&lt;td&gt;Higher (each HTTP request)&lt;/td&gt;&lt;td&gt;Lower (persistent connection)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Broadcast support&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Requires an API&lt;/td&gt;&lt;td&gt;Yes, views or REST API&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;In practical terms, htmx is simpler to set up and use for basic interactions. However, when you want to tackle complex tasks, the complexity rises sharply. Django LiveView, on the other hand, needs more initial setup, but once it&amp;#39;s running the curve is practically flat. The logic behind sending a message to every connected client is trivial: it looks a lot like opening a modal.&lt;/p&gt;&lt;h2&gt;Case 1: Update content with a click&lt;/h2&gt;&lt;p&gt;The most basic example. A button that updates a &lt;code&gt;div&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;htmx&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;div id=&amp;quot;content&amp;quot;&amp;gt;Initial content&amp;lt;/div&amp;gt; &amp;lt;button hx-get=&amp;quot;/update-content&amp;quot; hx-target=&amp;quot;#content&amp;quot;&amp;gt; Update &amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# views.py def update_content(request): return HttpResponse(&amp;quot;&amp;lt;p&amp;gt;Updated content&amp;lt;/p&amp;gt;&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# urls.py path(&amp;#39;update-content/&amp;#39;, update_content),&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Django LiveView&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Base template --&amp;gt; {% load static liveview %} &amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&amp;quot;en&amp;quot; data-room=&amp;quot;{% liveview_room_uuid %}&amp;quot;&amp;gt; &amp;lt;head&amp;gt; &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt; &amp;lt;/head&amp;gt; &amp;lt;body data-controller=&amp;quot;page&amp;quot;&amp;gt; &amp;lt;div id=&amp;quot;content&amp;quot;&amp;gt;{{ content }}&amp;lt;/div&amp;gt; &amp;lt;button data-liveview-function=&amp;quot;update_content&amp;quot; data-action=&amp;quot;click-&amp;gt;page#run&amp;quot;&amp;gt; Update &amp;lt;/button&amp;gt; &amp;lt;script src=&amp;quot;{% static &amp;#39;liveview/liveview.min.js&amp;#39; %}&amp;quot; defer&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;data-room&lt;/code&gt; attribute generates a unique identifier per page load, and &lt;code&gt;data-controller=&amp;quot;page&amp;quot;&lt;/code&gt; activates the controller that listens for events. Both go in the base HTML; the rest of your templates no longer repeat them.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# handlers.py from liveview.decorators import liveview_handler from liveview.connections import send @liveview_handler(&amp;quot;update_content&amp;quot;) def update_content(consumer, content): send(consumer, { &amp;quot;target&amp;quot;: &amp;quot;#content&amp;quot;, &amp;quot;html&amp;quot;: &amp;quot;&amp;lt;p&amp;gt;Updated content&amp;lt;/p&amp;gt;&amp;quot;, })&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice how htmx needs a specific route, while LiveView handles everything over the WebSocket with a decorator. The handler doesn&amp;#39;t return an HTTP response: it calls &lt;code&gt;send()&lt;/code&gt; to push the HTML to the client, and it can call it as many times as it wants.&lt;/p&gt;&lt;h2&gt;Case 2: Form with validation&lt;/h2&gt;&lt;p&gt;A form that validates on the server without reloading the page.&lt;/p&gt;&lt;h3&gt;htmx&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;form hx-post=&amp;quot;/validate-form&amp;quot; hx-target=&amp;quot;#errors&amp;quot;&amp;gt; &amp;lt;input type=&amp;quot;email&amp;quot; name=&amp;quot;email&amp;quot;&amp;gt; &amp;lt;div id=&amp;quot;errors&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;Submit&amp;lt;/button&amp;gt; &amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# views.py def validate_form(request): email = request.POST.get(&amp;#39;email&amp;#39;) if not email or &amp;#39;@&amp;#39; not in email: return HttpResponse(&amp;#39;&amp;lt;p style=&amp;quot;color:red;&amp;quot;&amp;gt;Invalid email&amp;lt;/p&amp;gt;&amp;#39;) return HttpResponse(&amp;#39;&amp;lt;p style=&amp;quot;color:green;&amp;quot;&amp;gt;Valid email&amp;lt;/p&amp;gt;&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# urls.py path(&amp;#39;validate-form/&amp;#39;, validate_form),&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Django LiveView&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;form&amp;gt; &amp;lt;input type=&amp;quot;email&amp;quot; name=&amp;quot;email&amp;quot;&amp;gt; &amp;lt;div id=&amp;quot;errors&amp;quot;&amp;gt;{{ error_message }}&amp;lt;/div&amp;gt; &amp;lt;button type=&amp;quot;submit&amp;quot; data-liveview-function=&amp;quot;validate_form&amp;quot; data-action=&amp;quot;click-&amp;gt;page#run&amp;quot;&amp;gt;Submit&amp;lt;/button&amp;gt; &amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# handlers.py from liveview.decorators import liveview_handler from liveview.connections import send @liveview_handler(&amp;quot;validate_form&amp;quot;) def validate_form(consumer, content): email = content.get(&amp;quot;form&amp;quot;, {}).get(&amp;quot;email&amp;quot;, &amp;quot;&amp;quot;) if not email or &amp;#39;@&amp;#39; not in email: error_html = &amp;#39;&amp;lt;p style=&amp;quot;color:red;&amp;quot;&amp;gt;Invalid email&amp;lt;/p&amp;gt;&amp;#39; else: error_html = &amp;#39;&amp;lt;p style=&amp;quot;color:green;&amp;quot;&amp;gt;Valid email&amp;lt;/p&amp;gt;&amp;#39; send(consumer, { &amp;quot;target&amp;quot;: &amp;quot;#errors&amp;quot;, &amp;quot;html&amp;quot;: error_html, })&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;LiveView serializes the form closest to the button and hands it to you in &lt;code&gt;content[&amp;quot;form&amp;quot;]&lt;/code&gt;, a dictionary keyed by each field&amp;#39;s &lt;code&gt;name&lt;/code&gt;. You don&amp;#39;t need &lt;code&gt;request.POST&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;Case 3: Real-time search&lt;/h2&gt;&lt;p&gt;Search that updates as you type.&lt;/p&gt;&lt;h3&gt;htmx&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;query&amp;quot; hx-get=&amp;quot;/search&amp;quot; hx-trigger=&amp;quot;keyup changed delay:500ms&amp;quot; hx-target=&amp;quot;#results&amp;quot;&amp;gt; &amp;lt;div id=&amp;quot;results&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# views.py def search(request): query = request.GET.get(&amp;#39;query&amp;#39;, &amp;#39;&amp;#39;) results = Article.objects.filter(title__icontains=query)[:5] return render(request, &amp;#39;search_results.html&amp;#39;, {&amp;#39;results&amp;#39;: results})&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# urls.py path(&amp;#39;search/&amp;#39;, search),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;delay:500ms&lt;/code&gt; attribute prevents making a request on every keystroke.&lt;/p&gt;&lt;h3&gt;Django LiveView&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;query&amp;quot; data-liveview-function=&amp;quot;search&amp;quot; data-action=&amp;quot;input-&amp;gt;page#run&amp;quot; data-liveview-debounce=&amp;quot;500&amp;quot;&amp;gt; &amp;lt;div id=&amp;quot;results&amp;quot;&amp;gt;{% include &amp;#39;search_results.html&amp;#39; %}&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# handlers.py from django.template.loader import render_to_string from liveview.decorators import liveview_handler from liveview.connections import send from .models import Article @liveview_handler(&amp;quot;search&amp;quot;) def search(consumer, content): query = content.get(&amp;quot;form&amp;quot;, {}).get(&amp;quot;query&amp;quot;, &amp;quot;&amp;quot;) results = Article.objects.filter(title__icontains=query)[:5] html = render_to_string(&amp;#39;search_results.html&amp;#39;, {&amp;#39;results&amp;#39;: results}) send(consumer, { &amp;quot;target&amp;quot;: &amp;quot;#results&amp;quot;, &amp;quot;html&amp;quot;: html, })&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here the trigger goes on the &lt;code&gt;input&lt;/code&gt; itself (&lt;code&gt;input-&amp;gt;page#run&lt;/code&gt;). The &lt;code&gt;data-liveview-debounce=&amp;quot;500&amp;quot;&lt;/code&gt; attribute serves the same purpose as &lt;code&gt;delay:500ms&lt;/code&gt; in htmx.&lt;/p&gt;&lt;h2&gt;Case 4: Automatic update (polling)&lt;/h2&gt;&lt;p&gt;Content that updates periodically.&lt;/p&gt;&lt;h3&gt;htmx&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;div hx-get=&amp;quot;/stats&amp;quot; hx-trigger=&amp;quot;every 2s&amp;quot; id=&amp;quot;stats&amp;quot;&amp;gt; {{ stats }} &amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# views.py def stats(request): active_users = get_active_users() return HttpResponse(f&amp;#39;&amp;lt;p&amp;gt;Active users: {active_users}&amp;lt;/p&amp;gt;&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# urls.py path(&amp;#39;stats/&amp;#39;, stats),&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Django LiveView&lt;/h3&gt;&lt;p&gt;Django LiveView has no automatic client-side polling. The philosophy is different: the server broadcasts when something changes. If you really want a periodic pulse, you run it as its own process, not inside the web server:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# management/commands/broadcast_stats.py from time import sleep from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from django.core.management.base import BaseCommand class Command(BaseCommand): help = &amp;quot;Send the number of active users to every client every 2 seconds&amp;quot; def handle(self, *args, **options): channel_layer = get_channel_layer() while True: sleep(2) active_users = get_active_users() html = f&amp;quot;&amp;lt;p&amp;gt;Active users: {active_users}&amp;lt;/p&amp;gt;&amp;quot; # &amp;#39;broadcast&amp;#39; is the group LiveView joins every client to; # &amp;#39;broadcast_message&amp;#39; is the consumer handler. async_to_sync(channel_layer.group_send)( &amp;quot;broadcast&amp;quot;, { &amp;quot;type&amp;quot;: &amp;quot;broadcast_message&amp;quot;, &amp;quot;message&amp;quot;: {&amp;quot;target&amp;quot;: &amp;quot;#stats&amp;quot;, &amp;quot;html&amp;quot;: html}, }, )&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;div id=&amp;quot;stats&amp;quot;&amp;gt;{{ stats }}&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You start it as a standalone process (&lt;code&gt;python manage.py broadcast_stats&lt;/code&gt;), managed by systemd or supervisor. Don&amp;#39;t put it in a thread inside the ASGI server: with several workers you&amp;#39;d have one copy of the loop per worker, each broadcasting on its own. Even so, this approach is more powerful than polling: every client gets the update at the same time, without each one asking on its own.&lt;/p&gt;&lt;h2&gt;Case 5: SPA navigation&lt;/h2&gt;&lt;p&gt;Navigate without reloading the whole page.&lt;/p&gt;&lt;h3&gt;htmx&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Base template --&amp;gt; &amp;lt;nav hx-boost:inherited=&amp;quot;true&amp;quot;&amp;gt; &amp;lt;a href=&amp;quot;/about&amp;quot;&amp;gt;About us&amp;lt;/a&amp;gt; &amp;lt;a href=&amp;quot;/contact&amp;quot;&amp;gt;Contact&amp;lt;/a&amp;gt; &amp;lt;/nav&amp;gt; &amp;lt;div id=&amp;quot;content&amp;quot;&amp;gt; &amp;lt;!-- Content --&amp;gt; &amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# views.py def about(request): return render(request, &amp;#39;about.html&amp;#39;) def contact(request): return render(request, &amp;#39;contact.html&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# urls.py path(&amp;#39;about/&amp;#39;, about), path(&amp;#39;contact/&amp;#39;, contact),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;hx-boost&lt;/code&gt; turns normal links into AJAX requests. htmx intercepts the click, makes a GET request and replaces the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; with the response content. Notice the &lt;code&gt;:inherited&lt;/code&gt; suffix: in htmx 4 it&amp;#39;s required for the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; attribute to apply to the child links. In htmx 2, &lt;code&gt;hx-boost=&amp;quot;true&amp;quot;&lt;/code&gt; was enough, because inheritance was automatic.&lt;/p&gt;&lt;h3&gt;Django LiveView&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;nav&amp;gt; &amp;lt;a href=&amp;quot;#&amp;quot; data-liveview-function=&amp;quot;load_about&amp;quot; data-action=&amp;quot;click-&amp;gt;page#run&amp;quot;&amp;gt; About us &amp;lt;/a&amp;gt; &amp;lt;a href=&amp;quot;#&amp;quot; data-liveview-function=&amp;quot;load_contact&amp;quot; data-action=&amp;quot;click-&amp;gt;page#run&amp;quot;&amp;gt; Contact &amp;lt;/a&amp;gt; &amp;lt;/nav&amp;gt; &amp;lt;div id=&amp;quot;content&amp;quot;&amp;gt; &amp;lt;!-- Content --&amp;gt; &amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# handlers.py from django.template.loader import render_to_string from liveview.decorators import liveview_handler from liveview.connections import send @liveview_handler(&amp;quot;load_about&amp;quot;) def load_about(consumer, content): html = render_to_string(&amp;#39;about.html&amp;#39;) send(consumer, { &amp;quot;target&amp;quot;: &amp;quot;#content&amp;quot;, &amp;quot;html&amp;quot;: html, }) @liveview_handler(&amp;quot;load_contact&amp;quot;) def load_contact(consumer, content): html = render_to_string(&amp;#39;contact.html&amp;#39;) send(consumer, { &amp;quot;target&amp;quot;: &amp;quot;#content&amp;quot;, &amp;quot;html&amp;quot;: html, })&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Case 6: Shared state between users&lt;/h2&gt;&lt;p&gt;Multiple users viewing data in real time.&lt;/p&gt;&lt;h3&gt;htmx&lt;/h3&gt;&lt;p&gt;No support in the core. You have to fall back on Server-Sent Events (SSE) or WebSockets through extensions (htmx 4 adds the &lt;code&gt;hx-live&lt;/code&gt; extension for this), which steps outside htmx&amp;#39;s request/response model.&lt;/p&gt;&lt;h3&gt;Django LiveView&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# handlers.py from liveview.decorators import liveview_handler from liveview.connections import send @liveview_handler(&amp;quot;add_message&amp;quot;) def add_message(consumer, content): message_text = content.get(&amp;quot;form&amp;quot;, {}).get(&amp;quot;message&amp;quot;, &amp;quot;&amp;quot;) # broadcast=True sends the message to every connected client send( consumer, { &amp;quot;target&amp;quot;: &amp;quot;#messages&amp;quot;, &amp;quot;html&amp;quot;: f&amp;#39;&amp;lt;p&amp;gt;{message_text}&amp;lt;/p&amp;gt;&amp;#39;, &amp;quot;append&amp;quot;: True, }, broadcast=True, )&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;div id=&amp;quot;messages&amp;quot;&amp;gt; &amp;lt;!-- Messages appear here --&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;form&amp;gt; &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;message&amp;quot;&amp;gt; &amp;lt;button type=&amp;quot;submit&amp;quot; data-liveview-function=&amp;quot;add_message&amp;quot; data-action=&amp;quot;click-&amp;gt;page#run&amp;quot;&amp;gt;Send&amp;lt;/button&amp;gt; &amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All connected users receive the message instantly.&lt;/p&gt;&lt;h2&gt;Case 7: File handling&lt;/h2&gt;&lt;h3&gt;htmx&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form hx-post=&amp;quot;/upload&amp;quot; hx-encoding=&amp;quot;multipart/form-data&amp;quot; hx-target=&amp;quot;#result&amp;quot;&amp;gt; &amp;lt;input type=&amp;quot;file&amp;quot; name=&amp;quot;file&amp;quot;&amp;gt; &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;Upload&amp;lt;/button&amp;gt; &amp;lt;/form&amp;gt; &amp;lt;div id=&amp;quot;result&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# views.py def upload(request): if request.method == &amp;#39;POST&amp;#39;: uploaded_file = request.FILES[&amp;#39;file&amp;#39;] # Process file return HttpResponse(f&amp;#39;&amp;lt;p&amp;gt;File {uploaded_file.name} uploaded&amp;lt;/p&amp;gt;&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# urls.py path(&amp;#39;upload/&amp;#39;, upload),&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Django LiveView&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Template --&amp;gt; &amp;lt;form enctype=&amp;quot;multipart/form-data&amp;quot;&amp;gt; &amp;lt;input type=&amp;quot;file&amp;quot; name=&amp;quot;file&amp;quot;&amp;gt; &amp;lt;button type=&amp;quot;submit&amp;quot; data-liveview-function=&amp;quot;upload_file&amp;quot; data-action=&amp;quot;click-&amp;gt;page#run&amp;quot;&amp;gt;Upload&amp;lt;/button&amp;gt; &amp;lt;/form&amp;gt; &amp;lt;div id=&amp;quot;result&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# handlers.py from liveview.decorators import liveview_handler from liveview.connections import send @liveview_handler(&amp;quot;upload_file&amp;quot;) def upload_file(consumer, content): uploaded_file = content.get(&amp;quot;form&amp;quot;, {}).get(&amp;quot;file&amp;quot;) if uploaded_file: # Process file send(consumer, { &amp;quot;target&amp;quot;: &amp;quot;#result&amp;quot;, &amp;quot;html&amp;quot;: f&amp;#39;&amp;lt;p&amp;gt;File {uploaded_file} uploaded&amp;lt;/p&amp;gt;&amp;#39;, })&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here htmx has the edge: it uploads the file over HTTP with &lt;code&gt;multipart/form-data&lt;/code&gt; with no tricks. Over a WebSocket, the file travels inside the JSON message, so for large files htmx&amp;#39;s HTTP request is still the more comfortable option.&lt;/p&gt;&lt;h2&gt;htmx already has WebSockets&lt;/h2&gt;&lt;p&gt;Both can speak over WebSocket, but it doesn&amp;#39;t mean the same thing when they do.&lt;/p&gt;&lt;p&gt;In htmx, HTTP is the default transport and the WebSocket is an extension you enable on a specific element (in htmx 4, the &lt;code&gt;hx-live&lt;/code&gt; extension). You connect that part of the page to a socket, and the incoming messages carry HTML that htmx inserts. The rest of the application stays on HTTP. On top of that, you write the server consumer yourself: you define the message format, the rooms and the broadcasting.&lt;/p&gt;&lt;p&gt;In Django LiveView, the WebSocket is the transport for the whole page. Every interaction travels over it, not just the real-time bits. The framework manages the connection, the rooms, the broadcast and the reconnection; you only write handlers and call &lt;code&gt;send()&lt;/code&gt;. That&amp;#39;s why broadcasting to everyone is a single line (&lt;code&gt;send(..., broadcast=True)&lt;/code&gt;) instead of wiring up a consumer by hand.&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;htmx + WebSocket extension&lt;/th&gt;&lt;th&gt;Django LiveView&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Automatic connection and reconnection&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Send events and update the DOM with HTML&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Per-connection state on the server&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Broadcast to all clients&lt;/td&gt;&lt;td&gt;🟡 you build it&lt;/td&gt;&lt;td&gt;✅ &lt;code&gt;broadcast=True&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rooms or client groups&lt;/td&gt;&lt;td&gt;🟡 you build it&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;History and URL with snapshots and scroll restoration&lt;/td&gt;&lt;td&gt;❌ (htmx&amp;#39;s history is from its HTTP mode)&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Event debounce&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;✅ &lt;code&gt;data-liveview-debounce&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Visibility triggers (scroll or intersection)&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;✅ &lt;code&gt;data-liveview-intersect&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Keyboard shortcut maps&lt;/td&gt;&lt;td&gt;🟡 key filters&lt;/td&gt;&lt;td&gt;✅ &lt;code&gt;data-liveview-keyboard-map&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Focus management after a DOM update&lt;/td&gt;&lt;td&gt;🟡 &lt;code&gt;hx-preserve&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ &lt;code&gt;data-liveview-focus&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;In short, in htmx the WebSocket is an add-on for specific parts of an app that stays on HTTP; in LiveView it&amp;#39;s the backbone.&lt;/p&gt;&lt;h2&gt;Final notes&lt;/h2&gt;&lt;p&gt;You can keep SSR for some pages, htmx in some components and LiveView in others. One technology doesn&amp;#39;t rule out the other. Both are complementary and aim for maximum simplicity within their paradigms.&lt;/p&gt;&lt;p&gt;That said, they&amp;#39;re not interchangeable: you can&amp;#39;t use htmx to update a component managed by LiveView, and vice versa. In the end, it&amp;#39;s you who has to decide which one fits each case best. But whichever you pick, let it be because you have experience in different paradigms and not because you don&amp;#39;t know the other option.&lt;/p&gt;&lt;p&gt;Happy Hacking!&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;Help me keep writing Every coffee gives me a push toward the next article. &lt;a href=&quot;https://ko-fi.com/W7W02LB83&quot;&gt;Sure, it&amp;#39;s on me!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Send an email to &lt;a href=&quot;mailto:comment+article-94d14a9e@andros.dev&quot;&gt;comment+article-94d14a9e@andros.dev&lt;/a&gt; to leave a comment. The subject will be ignored.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Chess.com launched a poker site and is planning even more classic games</title>
<link>https://www.theverge.com/games/986302/chess-poker-expansion-gambit</link>
<guid isPermaLink="false">ojK6dD1aYRj38oNFJnO0qhFb0gGcSu_-p8KB2Q==</guid>
<pubDate>Sun, 30 Aug 2026 16:20:47 +0000</pubDate>
<description>Chess.com has done one thing, and one thing very well, for almost 20 years, but now it&#39;s going beyond its domain name. The biggest name in online chess quietly launched its first companion site in beta back in May: Gambit, a free online poker website where players can learn the game without risking any real […]</description>
<content:encoded>&lt;figure&gt;&lt;img src=&quot;https://platform.theverge.com/wp-content/uploads/sites/2/2026/08/chess-com-gambit-launch-bot-match.png?quality=90&amp;amp;strip=all&amp;amp;crop=0,0,100,100&quot; alt=&quot;A screenshot of a bot match on Gambit&quot; title=&quot;&quot;/&gt;&lt;/figure&gt;&lt;p&gt;Chess.com has done one thing, and one thing very well, for almost 20 years, but now it&amp;#39;s going beyond its domain name. The biggest name in online chess quietly launched its first companion site in beta back in May: &lt;a href=&quot;https://www.gambit.com/&quot;&gt;Gambit&lt;/a&gt;, a free online poker website where players can learn the game without risking any real money. Now Gambit&amp;#39;s ready to really get the ball rolling with its first livestreamed event and collaborations with content creators, and Chess.com is planning to branch out into even more games from here.&lt;/p&gt;&lt;p&gt;Chess.com&amp;#39;s chief growth officer, Albert Cheng, told &lt;em&gt;The Verge&lt;/em&gt; the company chose poker for its first companion site because, like ches …&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.theverge.com/games/986302/chess-poker-expansion-gambit&quot;&gt;Read the full story at The Verge.&lt;/a&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Vicariously hike the Appalachian in the gorgeous A Trail Tale</title>
<link>https://www.theverge.com/entertainment/986461/hike-appalachian-trail-pixel-art-a-trail-tale</link>
<guid isPermaLink="false">gQ2ZTO45nJxw5AcOrsg4L_D3XszdvlJjXPNzJw==</guid>
<pubDate>Sun, 30 Aug 2026 16:20:47 +0000</pubDate>
<description>I used to be an avid hiker and would try to go backpacking a few times a year. I always dreamed of thru-hiking the Appalachian Trail, but life kind of got in the way. (Turns out jobs, wives, and children aren&#39;t thrilled with the idea of you disappearing for three months.) But I&#39;ve been longing […]</description>
<content:encoded>&lt;figure&gt;&lt;img src=&quot;https://platform.theverge.com/wp-content/uploads/sites/2/2026/08/Screenshot-2026-08-29-at-4.57.57-PM.png?quality=90&amp;amp;strip=all&amp;amp;crop=0,0,100,100&quot; alt=&quot;A screenshot from A Trail Tale - a beautiful pixel art trail journal of the Appalachian Trail.&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;
	Inspired by Oregon Trail, but minus the dysentery. | Screenshot: Terrence O’Brien / The Verge	&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I used to be an &lt;a href=&quot;https://www.theverge.com/entertainment/936860/robert-moor-on-trails-book-review&quot;&gt;avid hiker&lt;/a&gt; and would try to go &lt;a href=&quot;https://www.theverge.com/2016/8/23/12588188/wilderness-week-camping-hiking-gear-best-of-round-up&quot;&gt;backpacking&lt;/a&gt; a few times a year. I always dreamed of &lt;a href=&quot;https://www.theverge.com/23630167/pacific-crest-trail-hiking-backpacking-gear-2600-miles&quot;&gt;thru-hiking&lt;/a&gt; the Appalachian Trail, but life kind of got in the way. (Turns out jobs, wives, and children aren&amp;#39;t thrilled with the idea of you disappearing for three months.) But I&amp;#39;ve been longing to reconnect with my love of the outdoors and just recently took my kids on their first camping trip. So I discovered &lt;em&gt;&lt;a href=&quot;https://atrailtale.com/#/&quot;&gt;A Trail Tale&lt;/a&gt;&lt;/em&gt; at a really opportune time. It&amp;#39;s an interactive trail journal created by Andy Moliski with help from his brothers Adam and Alex.&lt;/p&gt;&lt;p&gt;The various segments of the trail are rendered in beautiful pixel art reminiscent of a &amp;#39;90s S …&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.theverge.com/entertainment/986461/hike-appalachian-trail-pixel-art-a-trail-tale&quot;&gt;Read the full story at The Verge.&lt;/a&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Weboldal indításának jogi feltételei 2026: Teljes ellenőrző lista kezdőknek</title>
<link>https://www.awh.hu/blog/weboldal-inditasanak-jogi-feltetelei-2026-teljes-ellenorzo-lista-kezdoknek</link>
<guid isPermaLink="false">mUn8xGILWzEgyUUf3jVembRn538ScoJL4fWqGg==</guid>
<pubDate>Sat, 29 Aug 2026 18:55:54 +0000</pubDate>
<description>Ismerd meg a weboldal indításának jogi feltételei 2026-ban! Teljes ellenőrző listánk segít elkerülni a bírságokat az ÁSZF-től a GDPR-ig. Indulj magabiztosan!</description>
<content:encoded>&lt;p&gt;A weboldal indításának jogi feltételei 2026-ban már nem merülnek ki egy internetről letöltött sablon ÁSZF feltöltésében. Egyetlen apró technikai mulasztás vagy egy hiányzó online elállási nyilatkozat ma már azonnali fogyasztóvédelmi szankciókat és kötelező vételár-visszatérítést vonhat maga után. Tisztában vagyunk vele, hogy a bonyolult jogi bikkfanyelv és a világhálón keringő ellentmondásos információk miatt a kezdő vállalkozók gyakran bizonytalanok. Senki sem szeretné, ha az online jelenlét első hónapjait a NAIH vagy a hatóságok vizsgálatai és a súlyos bírságoktól való félelem árnyékolná be.&lt;/p&gt;&lt;p&gt;Ebből az útmutatóból pontosan megismerheted a magyarországi weboldal-indítás összes kötelező jogi elemét az ÁSZF-től a GDPR-ig, hogy elkerüld a büntetéseket és magabiztosan vágj bele a munkába. Nemcsak a kötelező dokumentumok tartalmát tisztázzuk, hanem segítünk a megbízható technikai alapok, például a GDPR-megfelelőséget egyszerűsítő hazai szerverpark és a biztonságos domain regisztráció kiválasztásában is. Végigvesszük a 2026-os legfrissebb szabályokat, az elállási jog új technikai követelményeit és az adattárolás kritikus kérdéseit, hogy te a növekedésre fókuszálhass a felesleges stressz helyett.&lt;/p&gt;&lt;h2&gt;Ki indíthat weboldalt Magyarországon? Alapvető vállalkozási feltételek&lt;/h2&gt;&lt;p&gt;Magyarországon bárki indíthat weboldalt, de a jogi kötelezettségek köre a tartalom céljától függ. Egy személyes blog, amely kizárólag hobbi célokat szolgál és nem tartalmaz hirdetéseket, nem igényel hivatalos vállalkozási formát. Amint azonban az oldalon bevételszerző tevékenység jelenik meg, a &lt;strong&gt;weboldal indításának jogi feltételei&lt;/strong&gt; azonnal szigorodnak. Gazdasági tevékenységnek minősül minden olyan folyamat, ahol terméket adsz el, szolgáltatást nyújtasz, vagy reklámfelületeket (például Google AdSense) és affiliate linkeket használsz.&lt;/p&gt;&lt;p&gt;A weboldal üzemeltetője jogilag lehet egyéni vállalkozó (EV), gazdasági társaság (például Kft. vagy Bt.), vagy bizonyos esetekben adószámos magánszemély. Az online kereskedelemhez elengedhetetlen a megfelelő tevékenységi körök bejelentése. Webáruház üzemeltetéséhez a 4791 - Csomagküldő, internetes kiskereskedelem TEÁOR szám felvétele kötelező. Ez a kód teszi lehetővé a számlaképességet, ami az online értékesítés alapköve. A technikai regisztráció során, például a &lt;a href=&quot;https://www.awh.hu/domain/hu-domain-regisztracio&quot;&gt;.hu domain regisztráció&lt;/a&gt; folyamatában, a tulajdonosi adatoknak pontosan egyezniük kell a hivatalos vállalkozási adatokkal.&lt;/p&gt;&lt;h3&gt;Magánszemélyként indított weboldalak jogi korlátai&lt;/h3&gt;&lt;p&gt;Magánszemélyként akkor indíthatsz oldalt vállalkozói igazolvány nélkül, ha a tevékenységed nem rendszeres és nem profitorientált. Fontos tudni, hogy a bevételszerzés legkisebb formája is adózási kötelezettséget von maga után. Ha hirdetéseket helyezel el az oldalon, már nem tekinthető tiszta hobbioldalnak, és az &lt;a href=&quot;https://hu.wikipedia.org/wiki/%C3%81ltal%C3%A1nos_adatv%C3%A9delmi_rendelet&quot;&gt;Általános adatvédelmi rendelet (GDPR)&lt;/a&gt; szabályai is szigorúbban vonatkoznak rád. Magánszemélyként az adózás bonyolultabb lehet, mert az önálló tevékenységből származó jövedelem után negyedévente adóelőleget kell fizetni a NAV felé.&lt;/p&gt;&lt;h3&gt;Bejelentési kötelezettségek a jegyző és a NAV felé&lt;/h3&gt;&lt;p&gt;2026-ban a kereskedelmi tevékenység megkezdését a székhely szerint illetékes jegyzőnél kell bejelenteni. Ez a folyamat ma már elektronikusan zajlik, de a bejelentés elmulasztása komoly bírságot vonhat maga után. Bizonyos termékkörök, például gyógyszerek vagy veszélyes anyagok értékesítése külön hatósági engedélyhez kötött. A távolsági értékesítés speciális szabályai előírják, hogy a NAV felé is jelezni kell az online csatornán történő eladást. A hatóságok 2026-ban kiemelt figyelmet fordítanak arra, hogy a weboldalon feltüntetett üzemeltetői adatok és a bejelentett tevékenységek teljes összhangban legyenek.&lt;/p&gt;&lt;p&gt;A jogszerű működés nemcsak a bürokráciáról szól, hanem a hitelességről is. A technikai háttér kiválasztásakor érdemes olyan hazai szolgáltatót választani, amelynek infrastruktúrája támogatja a magyar jogszabályoknak való megfelelést. A magyarországi szerverpark használata például jelentősen egyszerűsíti az adatkezelési tájékoztató elkészítését, mivel az adatok az országon belül maradnak.&lt;/p&gt;&lt;h2&gt;Kötelező tartalmi elemek: Az Impresszum és az ÁSZF követelményei&lt;/h2&gt;&lt;p&gt;A weboldal indításának jogi feltételei Magyarországon alapvetően az elektronikus kereskedelmi szolgáltatásokról szóló törvényen (Ekertv.) alapulnak. Sokan hiszik, hogy csak a webshopoknak vannak kötelezettségeik, de az igazság az, hogy minden gazdasági célú honlapnak rendelkeznie kell bizonyos alapvető dokumentumokkal. Ezek hiánya nemcsak bizalmatlanságot szül, hanem közvetlen utat jelent a hatósági ellenőrzésekhez. A &lt;a href=&quot;https://www.naih.hu/&quot;&gt;Nemzeti Adatvédelmi és Információszabadság Hatóság (NAIH)&lt;/a&gt; és a fogyasztóvédelem 2026-ban még szigorúbban ellenőrzi a kötelező tájékoztatási elemek meglétét és azok frissességét.&lt;/p&gt;&lt;h3&gt;Az Impresszum kötelező elemei pontról pontra&lt;/h3&gt;&lt;p&gt;Az Impresszum a weboldal “személyi igazolványa”. Ez a dokumentum teszi lehetővé, hogy a látogatók és a hatóságok azonosíthassák az üzemeltetőt. A törvényi előírások szerint az alábbi adatokat kötelező jól látható módon közzétenni:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A szolgáltató hivatalos neve és székhelye.&lt;/li&gt;&lt;li&gt;Elérhetőségek: működő e-mail cím és telefonszám.&lt;/li&gt;&lt;li&gt;Adószám és cégjegyzékszám (vagy egyéni vállalkozói nyilvántartási szám).&lt;/li&gt;&lt;li&gt;A nyilvántartásba vevő hatóság megnevezése.&lt;/li&gt;&lt;li&gt;A tárhelyszolgáltató neve, székhelye és elérhetősége.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Kiemelten fontos, hogy a tárhelyszolgáltató pontos adatait is feltüntesd. Nem elegendő csak a nevet kiírni; a jogszabály a fizikai címet és a kapcsolatfelvételi adatokat is megköveteli. Ha a technikai hátteret egy megbízható partner biztosítja, az adatok feltüntetése a jogi megfelelőség mellett a szakmai stabilitást is jelzi. Érdemes már az induláskor olyan &lt;a href=&quot;https://www.awh.hu/hosting/webhosting&quot;&gt;biztonságos webtárhely&lt;/a&gt; szolgáltatást választani, amely minden szükséges adatot transzparensen rendelkezésre bocsát az impresszumodhoz.&lt;/p&gt;&lt;h3&gt;ÁSZF webshopoknak: A leggyakoribb hibák, amikért bírság jár&lt;/h3&gt;&lt;p&gt;Az Általános Szerződési Feltételek (ÁSZF) szabályozzák a vásárló és a kereskedő közötti jogviszonyt. 2026-ban több jelentős változás lépett életbe. Január 1-jétől az ÁSZF-nek kötelezően tartalmaznia kell a termék értékcsökkenéséért való felelősség szabályait elállás esetén. Ha ez hiányzik, a webshop nem követelhet kártérítést a használtan visszaküldött termékek után. Június 19-től pedig kötelező egy folyamatosan elérhető online elállási űrlap biztosítása, amely automatikus visszaigazolást küld a vásárlónak.&lt;/p&gt;&lt;p&gt;A jótállási szabályok is szigorodtak: március 1-jétől bármilyen típusú jótállási igényt legfeljebb 30 napon belül le kell zárni. Ha a javítás ezen belül nem készül el, kötelező a csere vagy a vételár visszatérítése. Az online vitarendezési (ODR) platform linkjét viszont már nem kell szerepeltetni, mivel ez a rendszer 2025 júliusában megszűnt. A panaszkezelési módok leírása és a 14 napos elállási jog pontos, mintatájékoztatóval kiegészített megfogalmazása továbbra is a &lt;strong&gt;weboldal indításának jogi feltételei&lt;/strong&gt; közé tartozik, aminek elhanyagolása súlyos büntetéseket vonhat maga után.&lt;/p&gt;&lt;h2&gt;GDPR és adatvédelem: A látogatók adatainak biztonságos kezelése&lt;/h2&gt;&lt;p&gt;Az adatvédelem 2026-ban már nem csupán egy kötelezően kipipálandó dokumentumot jelent, hanem a technikai és jogi megoldások szoros egységét. A &lt;strong&gt;weboldal indításának jogi feltételei&lt;/strong&gt; közül az egyik legkritikusabb pont a felhasználói adatok átlátható kezelése. Nem elegendő egy általános tájékoztatót közzétenni; az adatkezelés célját, jogalapját és pontos időtartamát minden folyamatnál (például vásárlás, hírlevél, kapcsolatfelvétel) külön kell nevesíteni. A &lt;a href=&quot;https://drbankuti.hu/&quot;&gt;weboldal üzemeltetés jogi feltételei&lt;/a&gt; szempontjából alapvető elvárás, hogy a látogató pontosan tudja, mi történik az adataival a rögzítéstől a törlésig.&lt;/p&gt;&lt;p&gt;A technikai megfelelőség ott kezdődik, hogy hol tárolod ezeket az adatokat. Egy magyarországi szerverparkkal rendelkező szolgáltató, mint az aWh, használata jelentős előnyt jelent a GDPR-megfelelőségben, mivel az adatok nem hagyják el az Európai Unió területét. A tárhelyszolgáltatóval kötött írásos adatfeldolgozói szerződés megléte elengedhetetlen. Emellett a biztonságos adatátvitelhez elengedhetetlen egy érvényes &lt;a href=&quot;https://www.awh.hu/ssl&quot;&gt;SSL tanúsítvány&lt;/a&gt;, amely titkosítja a felhasználó és a szerver közötti kommunikációt.&lt;/p&gt;&lt;h3&gt;Mikor válsz adatkezelővé? (Már egy kapcsolatfelvételi űrlap is számít!)&lt;/h3&gt;&lt;p&gt;Sokan tévesen azt hiszik, hogy csak a webshopok számítanak adatkezelőnek. Valójában amint a weboldalad rögzíti a látogató IP címét, vagy lehetőséget ad egy e-mail cím megadására, te már adatkezelővé válsz. A személyes adatok köre széles: a név és a telefonszám mellett az online azonosítók is ide tartoznak. Az Európai Adatvédelmi Testület (EDPB) 2026. július 7-én kiadott 02/2026-os iránymutatása szerint az adatok anonimizálása is adatkezelési műveletnek minősül, amelyhez jogalap szükséges. Ez azt jelenti, hogy ha a jövőben statisztikai célból anonimizálni tervezed a felhasználói adatokat, erről már az eredeti tájékoztatóban informálnod kell az érintetteket.&lt;/p&gt;&lt;h3&gt;Sütik és nyomkövetők: A 2026-os megfelelőségi szintek&lt;/h3&gt;&lt;p&gt;A sütikezelés (Cookie Consent) terén 2026-ban a “privacy by design” elv dominál. A cookie banner nem takarhatja el a teljes tartalmat, és nem kényszerítheti a felhasználót az elfogadásra. Az érvényes hozzájárulás feltételei:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Aktív hozzájárulás:&lt;/strong&gt; Nincs előre beikszelt négyzet vagy automatikus elfogadás görgetésre.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Egyenrangú választás:&lt;/strong&gt; Az “Elutasítás” gombnak ugyanolyan könnyen elérhetőnek és láthatónak kell lennie, mint az “Elfogadás” gombnak.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Kategóriák szerinti bontás:&lt;/strong&gt; A statisztikai és marketing sütikhez külön-külön kell engedélyt kérni.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Naplózás:&lt;/strong&gt; A hozzájárulásokat technikai szinten naplózni kell, hogy egy esetleges NAIH ellenőrzés során bizonyítható legyen a jogkövető magatartás.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A hírlevél feliratkozásnál továbbra is a kettős megerősítés (double opt-in) a legbiztonságosabb módszer. Ez garantálja, hogy a felhasználó valóban saját szándékából adta meg az adatait. A direkt marketing célú megkeresésekhez minden esetben külön, kifejezett és önkéntes hozzájárulás szükséges, amelyet bármikor, egyetlen kattintással vissza kell tudni vonni.&lt;/p&gt;&lt;h2&gt;Technikai feltételek jogi szemmel: Domain, SSL és tárhely&lt;/h2&gt;&lt;p&gt;A weboldal elindítása nem csupán szoftveres és dizájnkérdés. A technikai infrastruktúra minden eleme, a választott névtől az adatok tárolásának fizikai helyéig, konkrét jogi felelősséget von maga után. A &lt;strong&gt;weboldal indításának jogi feltételei&lt;/strong&gt; szorosan összefonódnak a technikai alapokkal. Egy rosszul megválasztott domain név védjegybitorlási perhez vezethet, a titkosítatlan adatátvitel pedig súlyos adatvédelmi bírságot vonhat maga után a hatóságok részéről.&lt;/p&gt;&lt;p&gt;A felelősség megoszlik közted és a szolgáltató között. A tárhelyszolgáltató felel az infrastruktúra fizikai védelméért és a szerveroldali biztonságért. Ugyanakkor az alkalmazott szoftverek, például a WordPress vagy egy egyedi fejlesztésű webshop frissítése és biztonsági beállítása már a te feladatod. A jogi megfelelőséghez mindkét félnek precízen kell teljesítenie a saját területén, hiszen a technikai mulasztás jogi következményei téged terhelnek.&lt;/p&gt;&lt;h3&gt;Domain regisztráció és márkavédelem&lt;/h3&gt;&lt;p&gt;A névválasztás az első jogi csapda az indulás során. Sokan elkövetik azt a hibát, hogy olyan nevet regisztrálnak, amely hasonlít egy már létező márkára vagy bejegyzett védjegyre. Magyarországon a .hu domainek esetében a Domainregisztrációs Szabályzat az irányadó dokumentum. Vitás esetekben a Domaindöntőnök határoz, ami akár a név kényszerű és azonnali átadását is jelentheti a jogos tulajdonosnak. Érdemes alaposan tájékozódni a &lt;a href=&quot;https://www.awh.hu/blog/domain-nev-regisztracio-2026-teljes-utmutato-a-sikeres-online-indulashoz&quot;&gt;Domain név regisztráció útmutató&lt;/a&gt; segítségével, mielőtt véglegesítenéd a döntést. A védjegykutatás elengedhetetlen lépés, hogy elkerüld a más szellemi tulajdonát sértő elnevezéseket.&lt;/p&gt;&lt;h3&gt;HTTPS és titkosítás: Az adatvédelem alapköve&lt;/h3&gt;&lt;p&gt;A HTTPS protokoll használata 2026-ban már nem opcionális kényelmi funkció. Az &lt;a href=&quot;https://www.awh.hu/ssl&quot;&gt;SSL tanúsítvány&lt;/a&gt; biztosítja, hogy a látogatók által megadott adatok titkosítva jussanak el a felhasználótól a szerverre. A modern böngészők ma már “nem biztonságos” jelzéssel látják el a tanúsítvány nélküli oldalakat, ami nemcsak a felhasználói bizalmat rombolja, hanem jogi kockázatot is rejt. A személyes adatok védelmének elhanyagolása közvetlen alapot ad a NAIH bírságához. Az aWh felületén az SSL aktiválása néhány kattintással elvégezhető, így ez a jogi akadály gyorsan elhárítható.&lt;/p&gt;&lt;p&gt;A tárhelyszolgáltató kiválasztása kritikus pont a GDPR szempontjából is. Ha magyarországi szerverparkkal rendelkező szolgáltatót választasz, az adatok az Európai Unión belül maradnak. Ez jelentősen egyszerűsíti az adatfeldolgozói láncot és a jogi megfelelőséget. Az aWh több mint 10 éves tapasztalata és hazai infrastruktúrája garanciát jelent arra, hogy a technikai háttér megfelel a magyar és uniós elvárásoknak. Ha biztosra akarsz menni a technikai alapokkal, válassz egy &lt;a href=&quot;https://www.awh.hu/&quot;&gt;megbízható magyar tárhelyszolgáltatót&lt;/a&gt;, aki érti a hazai szabályozási környezetet.&lt;/p&gt;&lt;h2&gt;Weboldal indítási checklist 2026: Lépésről lépésre a jogtisztaságig&lt;/h2&gt;&lt;p&gt;A &lt;strong&gt;weboldal indításának jogi feltételei&lt;/strong&gt; 2026-ban egy strukturált folyamatot követelnek meg a tulajdonostól. A jogszabályi megfelelőség nem csupán a büntetések elkerülése miatt fontos; ez az alapja a vásárlói bizalomnak és a hosszú távú üzleti stabilitásnak is. Az alábbi ellenőrző lista segít abban, hogy egyetlen kritikus lépés se maradjon ki az indulás előtti lázas munkában. Haladj végig a pontokon, és győződj meg róla, hogy minden jogi és technikai pillér szilárdan áll.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Vállalkozási háttér:&lt;/strong&gt; Ellenőrizd, hogy a vállalkozási formád alkalmas-e online értékesítésre, és a 4791-es TEÁOR szám szerepel-e a tevékenységi köreid között.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Domain és védelem:&lt;/strong&gt; Végezz védjegykutatást a választott névvel kapcsolatban, és aktiváld az SSL tanúsítványt a biztonságos adatkapcsolathoz.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Jogi dokumentáció:&lt;/strong&gt; Készítsd el a 2026-os szabályoknak megfelelő ÁSZF-et (különös tekintettel az értékcsökkenési és jótállási szabályokra), az Impresszumot és a GDPR tájékoztatót.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hozzájáruláskezelés:&lt;/strong&gt; Állítsd be a sütikezelő (Cookie Consent) rendszert, amely lehetővé teszi a látogatóknak a granuláris választást és a döntések naplózását.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Adatfeldolgozói lánc:&lt;/strong&gt; Kérd be és archiváld a tárhelyszolgáltatódtól az adatfeldolgozói nyilatkozatot a jogszerű adatkezelés igazolásához.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;A technikai alapok biztosítása&lt;/h3&gt;&lt;p&gt;A jogi megfelelőség technikai stabilitást igényel. Első lépésként válaszd ki a céljaidnak leginkább megfelelő &lt;a href=&quot;https://www.awh.hu/hosting/webhosting&quot;&gt;webtárhely csomagot&lt;/a&gt;, amely garantálja a magyarországi adatkezelést. A regisztráció során ügyelj arra, hogy a domain tulajdonosi adatai megegyezzenek a vállalkozásod hivatalos adataival. Az aWh több mint 10 éves piaci tapasztalata és a modern hardveres infrastruktúra biztosítja azt a technológiai hátteret, amelyre a jogtisztaság épülhet. Ellenőrizd a szolgáltatási szintet (SLA) is, hiszen a weboldal folyamatos elérhetősége a fogyasztóvédelmi tájékoztatási kötelezettséged része.&lt;/p&gt;&lt;h3&gt;Utolsó ellenőrzés az élesítés előtt&lt;/h3&gt;&lt;p&gt;Mielőtt nyilvánossá tennéd az oldalt, végezz el egy alapos tesztelést. Győződj meg róla, hogy az Impresszum, az ÁSZF és a GDPR linkje minden aloldal láblécéből közvetlenül elérhető. Webshop üzemeltetése esetén teszteld a vásárlási folyamatot: megérkezik a jogszabályban előírt automatikus visszaigazolás? Tartalmazza a levél a szükséges tájékoztatókat? 2026-ban a hatóságok kiemelten figyelik az automatizált folyamatok jogi pontosságát. Végezetül készíts egy teljes biztonsági mentést a weboldal induló, jogtiszta állapotáról, hogy bármilyen későbbi módosítás esetén legyen egy stabil viszonyítási pontod. A &lt;strong&gt;weboldal indításának jogi feltételei&lt;/strong&gt; így nem akadályt, hanem biztonságos keretet jelentenek majd a vállalkozásodnak.&lt;/p&gt;&lt;h2&gt;Építs biztos alapokra és indítsd el weboldalad magabiztosan!&lt;/h2&gt;&lt;p&gt;A weboldal indításának jogi feltételei 2026-ban szigorúbbak, mint valaha, de a megfelelő felkészüléssel ezek az előírások nem akadályt, hanem komoly versenyelőnyt jelentenek. A legfontosabb tanulság, hogy a jogi megfelelőség nem ér véget a dokumentumok elkészítésénél. A technikai háttér, mint az SSL titkosítás és a szerverek fizikai helye, ugyanilyen súllyal esik latba. Ha az indulásnál a precizitást és az átláthatóságot választod, nemcsak a bírságokat kerülöd el, hanem a látogatóid bizalmát is elnyered.&lt;/p&gt;&lt;p&gt;Ne hagyd, hogy a bürokrácia elvegye a kedved a vállalkozástól. A technikai pillérek stabilizálásával a jogi folyamatok nagy része automatikusan egyszerűbbé válik. Mi az aWh-nál több mint 10 éves tapasztalattal segítünk a technikai megfelelőségben. Szolgáltatásainkkal &lt;strong&gt;100% GDPR megfelelőség támogatást&lt;/strong&gt; kapsz, minden csomagunkhoz &lt;strong&gt;ingyenes SSL tanúsítványt&lt;/strong&gt; biztosítunk, és bármilyen kérdés esetén &lt;strong&gt;magyar nyelvű ügyfélszolgálatunk&lt;/strong&gt; áll rendelkezésedre.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.awh.hu/hosting/webhosting&quot;&gt;Indítsd el jogtiszta weboldaladat megbízható aWh tárhelyen!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;A jogszabályok ismerete és a megbízható partner kiválasztása a legjobb befektetés a jövőbe. Sok sikert kívánunk az online jelenléted felépítéséhez!&lt;/p&gt;&lt;h2&gt;Gyakran Ismételt Kérdések&lt;/h2&gt;&lt;h3&gt;Kötelező-e az ÁSZF, ha nem adok el semmit a weboldalamon?&lt;/h3&gt;&lt;p&gt;Nem, az Általános Szerződési Feltételek (ÁSZF) csak akkor kötelező eleme a honlapnak, ha szerződés jön létre a felek között, például egy webáruház esetében. Ugyanakkor az Impresszum és az Adatkezelési tájékoztató megléte minden gazdasági célú oldalon elvárás, függetlenül attól, hogy történik-e közvetlen eladás. Ha az oldalad csak bemutatkozó jellegű, elegendő ezeket a dokumentumokat elkészítened a &lt;strong&gt;weboldal indításának jogi feltételei&lt;/strong&gt; szerint, hogy elkerüld a hatósági figyelmeztetéseket.&lt;/p&gt;&lt;h3&gt;Büntethetnek-e, ha nincs SSL tanúsítvány a weboldalamon?&lt;/h3&gt;&lt;p&gt;Igen, a hatóságok az SSL tanúsítvány hiányát súlyos adatbiztonsági mulasztásként értékelhetik, különösen ha az oldalon bármilyen űrlap vagy bejelentkezési felület található. A GDPR előírja a személyes adatok védelmét a továbbítás során is, amit a HTTPS protokoll biztosít. A titkosítatlan kapcsolat közvetlen kockázatot jelent a látogatók adataira nézve, ami elegendő alap a NAIH számára egy komolyabb pénzbírság kiszabásához és az adatkezelési folyamatok felfüggesztéséhez.&lt;/p&gt;&lt;h3&gt;Használhatok-e sablon ÁSZF-et az internetről?&lt;/h3&gt;&lt;p&gt;A sablonok használata rendkívül kockázatos, mivel a 2026-os szabálymódosításokat, például az online elállási űrlap technikai követelményeit, a régi internetes minták nem tartalmazzák pontosan. Minden üzleti modell egyedi folyamatokat igényel, így egy általános sablon soha nem nyújt teljes jogi védelmet. Érdemesebb szakjogász segítségét kérni, hogy a dokumentum minden aktuális fogyasztóvédelmi és adatvédelmi előírásnak megfeleljen, elkerülve a későbbi kellemetlen szankciókat és a fogyasztóvédelmi bírságokat.&lt;/p&gt;&lt;h3&gt;Milyen adatokat kell feltüntetnem az impresszumban egyéni vállalkozóként?&lt;/h3&gt;&lt;p&gt;Egyéni vállalkozóként az impresszumban fel kell tüntetned a teljes polgári nevedet, a székhelyed pontos címét, az adószámodat és az egyéni vállalkozói nyilvántartási számodat. Kötelező elem még a működő e-mail cím és egy telefonszám is a közvetlen hatósági kapcsolatfelvételhez. Ne feledkezz meg a tárhelyszolgáltatód, például az aWh nevének, székhelyének és elérhetőségének rögzítéséről sem, mert ennek hiányát a fogyasztóvédelem gyakran szankcionálja az ellenőrzések során.&lt;/p&gt;&lt;h3&gt;Szükséges-e a weboldal indítását külön bejelenteni a jegyzőnél?&lt;/h3&gt;&lt;p&gt;Igen, amennyiben a weboldalon kereskedelmi tevékenységet, például webáruházat indítasz, azt be kell jelentened a székhelyed szerint illetékes jegyzőnél. Ez a bejelentés a távollévők között kötött szerződések alapfeltétele, és ma már kényelmesen, elektronikus úton is intézhető a megfelelő nyomtatványokon. A bejelentés elmulasztása esetén a hatóság megtilthatja a termékek értékesítését, és súlyos mulasztási bírságot szabhat ki a vállalkozásra a szabálytalanság megszüntetéséig és a regisztráció pótlásáig.&lt;/p&gt;&lt;h3&gt;Mi történik, ha nem felel meg a weboldalam a GDPR szabályainak?&lt;/h3&gt;&lt;p&gt;Ha a weboldalad nem felel meg a GDPR szabályainak, a NAIH figyelmeztetést adhat ki, vagy súlyos pénzbírságot szabhat ki a mulasztás mértékétől függően. A hatóság elrendelheti az adatok törlését vagy az adatkezelési műveletek ideiglenes felfüggesztését is. A pénzügyi veszteség mellett a vállalkozás hírneve is sérül, hiszen a látogatók bizalma megrendül a nem biztonságosnak ítélt platformmal szemben, ami hosszú távú bevételkiesést okozhat a piacon.&lt;/p&gt;&lt;h3&gt;Kell-e külön adatvédelmi tisztviselőt (DPO) kineveznem?&lt;/h3&gt;&lt;p&gt;A legtöbb kis- és középvállalkozás számára nem kötelező adatvédelmi tisztviselőt kinevezni a weboldal üzemeltetéséhez. Erre csak abban az esetben van szükség, ha a fő tevékenységed az érintettek nagymértékű, szisztematikus megfigyelése vagy különleges adatok, például egészségügyi információk tömeges kezelése. Egy átlagos webshop vagy üzleti bemutatkozó oldal üzemeltetéséhez elegendő, ha a belső folyamataidat a GDPR alapelvei szerint alakítod ki, és rendelkezel a törvény által előírt tájékoztatókkal.&lt;/p&gt;&lt;h3&gt;Hogyan igazolhatom a tárhelyszolgáltatóm GDPR megfelelőségét?&lt;/h3&gt;&lt;p&gt;A legegyszerűbben egy írásos adatfeldolgozói szerződéssel igazolhatod a megfelelőséget, amelyet a szolgáltatónak kérésre biztosítania kell számodra. Fontos szempont a szerverek fizikai helyszíne is: a magyarországi szerverpark használata jelentősen megkönnyíti a bizonyítást egy esetleges hatósági ellenőrzés során. Az aWh több mint 10 éves piaci tapasztalata és hazai infrastruktúrája garanciát nyújt arra, hogy a technikai háttér mindenben megfelel az európai adatvédelmi elvárásoknak.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Hogyan tehető golyóállóvá a tárhely fiók biztonsága? Útmutató a védelemhez - aWh</title>
<link>https://www.awh.hu/blog/hogyan-teheto-golyoallova-a-tarhely-fiok-biztonsaga-utmutato-a-vedelemhez</link>
<enclosure type="image/jpeg" length="0" url="https://www.awh.hu/assets/og-image/posts/hogyan-teheto-golyoallova-a-tarhely-fiok-biztonsaga-utmutato-a-vedelemhez.png"></enclosure>
<guid isPermaLink="false">TN4JIsYTYP_8PNd3U7ISWkvTstOY9rup3vExgA==</guid>
<pubDate>Sat, 29 Aug 2026 18:55:54 +0000</pubDate>
<description>Erősítse meg a tárhely fiók biztonságát, és védje meg üzleti adatait! Útmutatónk segít megelőzni a feltörést, az adatvesztést és a kibertámadásokat.</description>
<content:encoded>&lt;p&gt;Tudta, hogy 2025 első felében Magyarország volt a 17. leginkább célba vett ország az európai kibertámadások listáján? Ez a statisztikai adat rávilágít arra, hogy a tárhely fiók biztonság nem csupán egy választható extra, hanem az üzleti folytonosság és az adatvédelem alapvető tartóoszlopa.&lt;/p&gt;&lt;p&gt;Teljesen érthető, ha tart a weboldala váratlan leállásától vagy a technikai bizonytalanságtól, amit a bonyolult cPanel beállítások okozhatnak. Az emailben érkező adathalász kísérletek és a kéretlen spamek elleni küzdelem ma már minden vállalkozó mindennapjainak része, a tét pedig a bizalmas üzleti adatok és a digitális hitelesség megőrzése.&lt;/p&gt;&lt;p&gt;Ebből az útmutatóból pontosan megtudhatja, hogyan építhet ki többszintű védelmet tárhely fiókja és levelezése köré a legmodernebb biztonsági protokollok alkalmazásával. Segítünk minimalizálni a feltörés kockázatát, hogy üzleti adatait végre teljes biztonságban tudhassa.&lt;/p&gt;&lt;p&gt;Végigvesszük a legfontosabb konfigurációs lépéseket, a kétlépcsős azonosítás előnyeit és a professzionális email szolgáltató kiválasztásának szempontjait, hogy digitális infrastruktúrája valóban golyóálló legyen.&lt;/p&gt;&lt;h2&gt;Miért a tárhely fiók biztonsága a digitális jelenléted alapja?&lt;/h2&gt;&lt;p&gt;A tárhely fiók nem csupán egy távoli tárolóhely a fájlok számára. Ez a digitális ökoszisztéma központi irányítója, ahol a weboldal forráskódja, az adatbázisok és a teljes üzleti levelezés összefut. Ha ez a központ sérül, a teljes online jelenlét kártyavárként omolhat össze. A &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; elhanyagolása olyan kockázat, amely közvetlen hatással van a napi bevételre és az ügyfelek bizalmára.&lt;/p&gt;&lt;p&gt;Egy sikeres feltörés következményei messze túlmutatnak a technikai kellemetlenségeken. Az adatvesztés mellett megnyílhat az út a zsarolóvírusok előtt, amelyek titkosítják az állományokat, és váltságdíjat követelnek a helyreállításért. A márka hírnevének sérülése pedig gyakran helyrehozhatatlan; a látogatók nehezen bízzák rá adataikat egy olyan felületre, amely korábban kártékony kódot terjesztett.&lt;/p&gt;&lt;p&gt;A biztonság hiánya a keresőoptimalizálási eredményeket is tönkreteszi. A Google algoritmusai azonnal észlelik, ha egy oldal feltört állapotba kerül. Ilyenkor a keresőmotor “nem biztonságos” jelzéssel látja el a találatot, vagy drasztikusabb esetben teljesen eltávolítja a webhelyet az indexéből. A professzionális &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_hosting_service&quot;&gt;web hosting security&lt;/a&gt; alapfeltétele annak, hogy a SEO erőfeszítések és a hirdetési költségek ne vesszenek kárba.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; fenntartása érdekében fontos tisztázni a közös felelősség elvét. Míg a szolgáltató a hardveres infrastruktúrát, a hálózat védelmét és a szerveroldali tűzfalakat garantálja, a felhasználó feladata a hozzáférési adatok védelme és a tárhelyen futó alkalmazások naprakészen tartása.&lt;/p&gt;&lt;h3&gt;A leggyakoribb támadási formák 2026-ban&lt;/h3&gt;&lt;p&gt;2026-ban a támadások skálázódása és az automatizált megoldások terjedése jelenti a legnagyobb fenyegetést. A brute force támadások során robotok ezrei próbálnak másodpercenként belépni a fiókba, kihasználva a gyenge jelszavakat és a hiányzó kétlépcsős azonosítást. Az adathalászat (phishing) szintén kifinomultabbá vált, gyakran a tárhelyszolgáltató nevében érkező, megtévesztően hiteles emailekkel próbálják kicsalni a belépési kódokat. Emellett a szerveroldali sebezhetőségek, például az elavult PHP verziók használata, továbbra is kritikus belépési pontot kínálnak a támadók számára.&lt;/p&gt;&lt;h3&gt;A megbízható magyar hosting előnyei&lt;/h3&gt;&lt;p&gt;Egy hazai szolgáltató választása jelentős biztonsági és operatív előnyökkel jár. Incidens esetén a magyar nyelvű, szakértő ügyfélszolgálat gyorsabb reakcióidőt és hatékonyabb problémamegoldást tesz lehetővé, ami kritikus az adatmentés és a helyreállítás során. A jogszabályi megfelelőség, különösen a GDPR előírásai, sokkal átláthatóbban érvényesíthetők, ha az adatok hazai szerverparkokban pihennek. Ha még csak most ismerkedsz a technikai háttérrel, érdemes elolvasni a &lt;a href=&quot;https://www.awh.hu/blog/webtarhely-a-teljes-utmutato-kezdoknek-2026-ban&quot;&gt;Webtárhely: A Teljes Útmutató Kezdőknek 2026-ban&lt;/a&gt; című írásunkat a biztos alapok lefektetéséhez.&lt;/p&gt;&lt;h2&gt;A hozzáférés védelme: Így zárd le a digitális kapukat&lt;/h2&gt;&lt;p&gt;A hozzáférés-kezelés az első és legfontosabb védelmi vonal, amikor a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; a tét. 2026-ban egy egyszerű jelszó már nem jelent valódi akadályt a kifinomult feltörő algoritmusok számára. Az ideális jelszó ma már legalább 16 karakterből áll, kis- és nagybetűk, számok, valamint speciális karakterek véletlenszerű kombinációja. Sokan elkövetik azt a hibát, hogy ugyanazt a kódot használják több felületen is. Ez óriási kockázat: ha egy kevésbé védett webshopból kiszivárognak az adatai, a támadók azonnal megpróbálják majd ugyanezt a belépést a tárhelyén is.&lt;/p&gt;&lt;p&gt;A jelszókezelő szoftverek, mint például a Bitwarden vagy a 1Password, használata ma már elengedhetetlen. Ezek az eszközök nemcsak tárolják, hanem generálják is a komplex karaktersorozatokat, így önnek nem kell tucatnyi bonyolult kódot megjegyeznie. A &lt;a href=&quot;https://devops.com/9-best-practices-for-web-hosting-security/&quot;&gt;web hosting best security practices&lt;/a&gt; követése során az egyik legfontosabb lépés az emberi tényező, vagyis a gyenge jelszavak választásának kiiktatása a folyamatból.&lt;/p&gt;&lt;h3&gt;Kétfaktoros azonosítás (2FA) beállítása cPanelen&lt;/h3&gt;&lt;p&gt;A kétfaktoros azonosítás (2FA) jelenleg a leghatékonyabb eszköz az illetéktelen belépések megakadályozására. Még ha egy támadó meg is szerzi a jelszavát, a telefonjára érkező egyedi kód nélkül nem tud bejelentkezni a rendszerbe. A beállítás folyamata gyors és egyszerű:&lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;Lépjen be a cPanel felületére, és a “Security” (Biztonság) részben keresse a “Two-Factor Authentication” menüpontot.&lt;/li&gt;
  &lt;li&gt;Telepítsen egy hitelesítő alkalmazást a telefonjára (például Google Authenticator vagy Authy), és olvassa be a képernyőn megjelenő QR-kódot.&lt;/li&gt;
  &lt;li&gt;A rendszer generál néhány egyszer használatos biztonsági kódot. Ezeket mindenképpen mentse el egy offline, biztonságos helyre, mert ezekre lesz szüksége, ha elveszítené a telefonját vagy nem férne hozzá az applikációhoz.&lt;/li&gt;
&lt;/ul&gt;&lt;h3&gt;IP-alapú korlátozás és biztonságos kapcsolat&lt;/h3&gt;&lt;p&gt;Ha fix IP-címmel rendelkezik, tovább szigoríthatja a belépési szabályokat. A cPanel lehetővé teszi, hogy a hozzáférést csak meghatározott IP-címekre korlátozza. Ez azt jelenti, hogy még érvényes jelszó és 2FA kód birtokában sem lehet belépni a fiókba egy ismeretlen hálózatról vagy országból. Ez a módszer drasztikusan csökkenti a távoli, automatizált támadások sikerességét.&lt;/p&gt;&lt;p&gt;A technikai védelem másik pillére a titkosított kommunikáció. Megfelelő &lt;a href=&quot;https://www.awh.hu/ssl&quot;&gt;SSL tanúsítványok&lt;/a&gt; nélkül a böngésző és a szerver közötti adatforgalom, beleértve a belépési adatokat is, nyílt szövegként utazik a hálózaton, ami könnyen lehallgatható. A &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; alapkövetelménye, hogy minden adatforgalom titkosított csatornán zajoljon. Ha maximális védelmet szeretne üzleti adatainak, válasszon professzionális &lt;a href=&quot;https://www.awh.hu/hosting/cpanel&quot;&gt;cPanel tárhely&lt;/a&gt; megoldásaink közül, ahol a biztonsági funkciók alapértelmezettek.&lt;/p&gt;&lt;h2&gt;Biztonságos email szolgáltató és a levelezés védelme&lt;/h2&gt;&lt;p&gt;Az üzleti levelezés védelme elválaszthatatlan a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; kérdésétől. Az email fiók gyakran a leggyengébb láncszem a védelmi láncban. Ha egy támadó hozzáfér a levelezéséhez, könnyedén kezdeményezhet jelszó-visszaállítást a tárhelyhez vagy más kritikus üzleti szolgáltatásokhoz. Egy valóban biztonságos email szolgáltató nemcsak tárolja az üzeneteket, hanem aktív védelmi réteget képez a teljes forgalom felett.&lt;/p&gt;&lt;p&gt;A technikai védelem alapkövetelménye a titkosított kapcsolat használata minden csatornán. Az IMAPS, POP3S és SMTPS protokollok garantálják, hogy az adatok a küldő eszköze és a szerver között ne legyenek lehallgathatók. Ez a titkosítás megvédi a belépési kódokat és az üzenetek tartalmát a közbeékelődéses támadásoktól. Emellett a szerveroldali, automatizált spamszűrés és vírusvédelem már azelőtt semlegesíti a fenyegetéseket, hogy azok elérnék a felhasználó postafiókját.&lt;/p&gt;&lt;p&gt;A dedikált email tárhely használata jelentős előnyt kínál a biztonság és a reputáció terén is. Míg az ingyenes vagy alacsony minőségű szolgáltatók szerverei gyakran kerülnek globális tiltólistára a rajtuk keresztül zajló spamtevékenység miatt, a professzionális megoldások tiszta IP-címeket és szigorú felügyeletet garantálnak. Ez biztosítja, hogy az ön levelei ne a partnerek levélszemét mappájában kössenek ki, megőrizve az üzleti kommunikáció hitelességét.&lt;/p&gt;&lt;h3&gt;Email hitelesítési protokollok: SPF, DKIM, DMARC&lt;/h3&gt;&lt;p&gt;A hitelesség megőrzéséhez három technikai beállítás elengedhetetlen a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; maximalizálása érdekében. Az SPF (Sender Policy Framework) rekord pontosan meghatározza, mely szerverek jogosultak emailt küldeni az adott domain nevében. A DKIM (DomainKeys Identified Mail) egy egyedi digitális aláírással látja el az üzeneteket, így a fogadó fél igazolni tudja, hogy a levél tartalma nem módosult az út során. Végül a DMARC (Domain-based Message Authentication, Reporting, and Conformance) ad utasítást a fogadó szervereknek arra, hogyan kezeljék a hitelesítésen elbukott leveleket. Ez a hármas rendszer hatékonyan gátolja meg, hogy bárki visszaéljen az ön nevével vagy cégnevével.&lt;/p&gt;&lt;h3&gt;Hogyan ismerd fel az adathalász emaileket?&lt;/h3&gt;&lt;p&gt;A legkorszerűbb technikai védelem mellett a felhasználói éberség is kulcsfontosságú. Mindig ellenőrizze a feladó valódi email címét, ne csak a megjelenített nevet, mert az utóbbi könnyen hamisítható. Legyen gyanús minden olyan megkeresés, amely sürgető hangvételben kér jelszómódosítást, banki adatokat vagy személyes információkat. Soha ne adjon meg érzékeny adatot emailben érkező linkre kattintva. Ilyen esetekben gépelje be manuálisan a szolgáltató címét a böngészőbe. A biztonságos és professzionális &lt;a href=&quot;https://www.awh.hu/hosting/email&quot;&gt;email szolgáltatások&lt;/a&gt; használatával az ilyen típusú kockázatok jelentősen csökkenthetők.&lt;/p&gt;&lt;h2&gt;Szerveroldali védelem és jogosultságkezelés&lt;/h2&gt;&lt;p&gt;A &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; nem ér véget az erős jelszavaknál. A fájlrendszer szintű védelem legalább ennyire kritikus. Az informatikai biztonság egyik alapköve a “legkisebb jogosultság” elve. Ez azt jelenti, hogy minden felhasználónak, folyamatnak vagy szkriptnek csak a feladata elvégzéséhez feltétlenül szükséges hozzáférést szabad biztosítani. Ha egy bővítménynek csak olvasási jogra van szüksége, ne adjunk neki írási vagy futtatási jogosultságot.&lt;/p&gt;&lt;p&gt;A fájlok és mappák jogosultságainak, azaz a CHMOD beállításoknak a helyes konfigurálása gátat szab a kártékony kódok terjedésének. Általános szabályként a mappák esetében a 755, a fájloknál pedig a 644-es érték tekinthető biztonságosnak. Kerülje a 777-es jogosultság használatát, mert az bárki számára teljes hozzáférést enged, ami hatalmas biztonsági rést üt a védelmen. Emellett felejtse el a hagyományos FTP kapcsolatot. Az FTP titkosítatlanul továbbítja az adatokat, így a jelszava könnyen lehallgatható. Használjon helyette SFTP (SSH File Transfer Protocol) kapcsolatot, amely biztonságos, titkosított csatornán keresztül mozgatja az állományokat.&lt;/p&gt;&lt;p&gt;Az elavult szoftverek jelentik a támadások legfőbb bemeneti pontját. 2026 augusztusában a PHP 8.5 a legfrissebb stabil verzió, a régebbi változatok használata pedig komoly kockázatot hordoz. A rendszeres frissítés nemcsak a teljesítményt javítja, hanem befoltozza azokat a biztonsági réseket is, amelyeket a támadók automatizált eszközökkel folyamatosan keresnek.&lt;/p&gt;&lt;h3&gt;WordPress biztonság: a legnépszerűbb CMS védelme&lt;/h3&gt;&lt;p&gt;A WordPress népszerűsége miatt a hackerek elsőszámú célpontja. A védekezés alapja az automatikus frissítések bekapcsolása mind a magrendszer, mind a bővítmények esetében. Egy jól konfigurált biztonsági bővítmény, mint a Wordfence vagy az iThemes Security, képes blokkolni a gyanús IP-címeket és monitorozni a fájlok módosításait. Ha professzionális alapokra szeretné helyezni oldalát, olvassa el a &lt;a href=&quot;https://www.awh.hu/blog/wordpress-tarhely-a-teljes-utmutato-a-gyors-es-biztonsagos-weboldalhoz-2026-ban&quot;&gt;WordPress tárhely: A teljes útmutató a gyors és biztonságos weboldalhoz 2026-ban&lt;/a&gt; cikkünket a további részletekért.&lt;/p&gt;&lt;h3&gt;A biztonsági mentés: az utolsó védvonal&lt;/h3&gt;&lt;p&gt;Nincs százszázalékos védelem, ezért a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; elengedhetetlen eleme a rendszeres mentés. A mentés akkor ér valamit, ha a tárhelytől fizikailag és logikailag is független, külső helyszínen tárolja. A cPanel felületén könnyen beállíthatók automatizált mentési folyamatok, amelyek napi vagy heti rendszerességgel archiválják az adatokat. A legfontosabb lépés azonban a mentések rendszeres tesztelése. Egy mentés csak akkor létezik, ha bizonyítottan vissza is állítható belőle az üzleti adat. Ne várja meg a bajt, válasszon stabil és biztonságos &lt;a href=&quot;https://www.awh.hu/hosting/webhosting&quot;&gt;webtárhely&lt;/a&gt; csomagjaink közül, ahol az automatizált mentés a szolgáltatás része.&lt;/p&gt;&lt;h2&gt;Biztonságos tárhely az aWh-nál: ahol az adataid jó kezekben vannak&lt;/h2&gt;&lt;p&gt;Az aWh több mint 10 éves piaci jelenléte során a biztonság iránti elkötelezettség vált szolgáltatásaink legfőbb védjegyévé. Megértettük, hogy a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; nem csupán szoftveres beállítások sorozata, hanem egy folyamatosan fejlődő technológiai ökoszisztéma. Infrastruktúránkat úgy alakítottuk ki, hogy az a legkifinomultabb támadásokkal szemben is stabil maradjon, miközben a felhasználó számára egyszerű kezelhetőséget biztosít.&lt;/p&gt;&lt;p&gt;Minden webtárhely csomagunk mögött professzionális hardveres háttér áll. Az NVMe alapú tárolók nemcsak a weboldalak villámgyors betöltődéséért felelnek, hanem a hagyományos meghajtókhoz képest magasabb fokú adatbiztonságot és stabilitást is garantálnak. A szervereinket ért fenyegetéseket folyamatos, 24 órás monitorozással szűrjük, az automatizált DDoS védelem pedig minden ügyfelünk számára alapértelmezett, így a hálózati szintű támadások már azelőtt elhárulnak, hogy elérnék az ön webhelyét.&lt;/p&gt;&lt;p&gt;Szakértő, magyar nyelvű ügyfélszolgálatunk nemcsak technikai kérdésekben segít, hanem aktív támogatást nyújt a biztonsági beállítások optimalizálásában is. Legyen szó egy gyanús tevékenység kivizsgálásáról vagy a jogosultságok finomhangolásáról, csapatunk tapasztalatára bármikor támaszkodhat.&lt;/p&gt;&lt;h3&gt;Automatizált biztonsági funkciók az aWh tárhelyeken&lt;/h3&gt;&lt;p&gt;A manuális védekezés ideje lejárt, ezért rendszereinkbe számos automatizált megoldást integráltunk. Minden nálunk regisztrált domainhez ingyenes SSL tanúsítványt biztosítunk, amely titkosítja a látogatók és a szerver közötti kommunikációt. Ez alapfeltétele a bizalomépítésnek és a modern keresőoptimalizálási elvárásoknak is.&lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Rendszeres biztonsági mentések:&lt;/strong&gt; Automatizált rendszereink napi szinten készítenek másolatot az adatairól, így egy esetleges hiba vagy támadás után minimális idő alatt visszaállítható a korábbi állapot.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Intuitív vezérlés:&lt;/strong&gt; A könnyen kezelhető &lt;a href=&quot;https://www.awh.hu/hosting/cpanel&quot;&gt;cPanel felület&lt;/a&gt; segítségével ön is teljes körű rálátást kap a biztonsági beállításokra, a kétfaktoros azonosítástól kezdve az IP-alapú korlátozásokig.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Aktív vírusvédelem:&lt;/strong&gt; Szerveroldali szűrőink folyamatosan ellenőrzik a fájlrendszert, hogy azonosítsák és semlegesítsék a kártékony kódokat.&lt;/li&gt;
&lt;/ul&gt;&lt;h3&gt;Válts biztonságosabb környezetre zökkenőmentesen&lt;/h3&gt;&lt;p&gt;Sokan tartanak attól, hogy a költözés bonyolult folyamat, de az aWh szakértői leveszik ezt a terhet a válláról. Segítünk a teljes weboldal és a levelezés zökkenőmentes migrációjában, miközben elvégezzük az elsődleges biztonsági auditot is. Megvizsgáljuk a meglévő rendszereit, és javaslatot teszünk a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; további erősítésére, hogy vállalkozása a lehető legkisebb kockázat mellett működhessen.&lt;/p&gt;&lt;p&gt;Személyre szabott tanácsadásunk keretében segítünk kiválasztani az üzleti igényeihez leginkább passzoló konfigurációt, legyen szó egy induló blogról vagy egy nagy forgalmú webshopról. Ne bízza a véletlenre digitális értékeit. &lt;a href=&quot;https://www.awh.hu/hosting/webhosting&quot;&gt;Válaszd az aWh biztonságos webtárhelyeit&lt;/a&gt;, és élvezd a nyugodt munkavégzést egy stabil szakmai háttérrel!&lt;/p&gt;&lt;h2&gt;Legyen a biztonság az üzleti sikered alapköve!&lt;/h2&gt;&lt;p&gt;A &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; nem egyetlen beállításon múlik, hanem a tudatos hozzáférés-kezelés, a naprakész szoftverkörnyezet és a professzionális email hitelesítés együttes erején. A kétfaktoros azonosítás bevezetése és a titkosított SFTP kapcsolat használata olyan alapvető lépések, amelyekkel a támadások jelentős része már az induláskor semlegesíthető. Ne feledje, a biztonság egy folyamatos folyamat, ahol a megelőzés mindig kifizetődőbb, mint az utólagos kármentés.&lt;/p&gt;&lt;p&gt;Ha nem szeretné a véletlenre bízni digitális értékeit, válasszon olyan partnert, aki technológiai hátterével és szakértelmével is támogatja önt. Az aWh több mint 10 éves szakmai tapasztalattal, díjmentes SSL tanúsítvánnyal és magyar nyelvű szakértői támogatással segít kiépíteni a legmagasabb szintű védelmet. Modern NVMe infrastruktúránk és automatizált mentési rendszereink garantálják, hogy adatai minden körülmények között elérhetők maradjanak.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.awh.hu/hosting/webhosting&quot;&gt;Fokozd weboldalad védelmét az aWh biztonságos tárhelyeivel!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Kezdje el a védelmi rétegek kialakítását még ma, és építsen stabil jövőt vállalkozásának egy valóban golyóálló digitális környezetben.&lt;/p&gt;&lt;h2&gt;Gyakran Ismételt Kérdések&lt;/h2&gt;&lt;h3&gt;Hogyan választható ki a legbiztonságosabb email szolgáltató?&lt;/h3&gt;&lt;p&gt;A biztonságos email szolgáltató kiválasztásakor az alábbi technikai szempontok a legfontosabbak:&lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;SPF, DKIM és DMARC protokollok támogatása a hitelesség megőrzéséért.&lt;/li&gt;
  &lt;li&gt;Szerveroldali spamszűrés és folyamatos vírusvédelem.&lt;/li&gt;
  &lt;li&gt;Titkosított IMAPS és SMTPS kapcsolatok biztosítása minden eszközön.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Egy profi szolgáltató nemcsak tárolja a leveleket, hanem aktív védelmet is nyújt a fenyegetések ellen. A dedikált email tárhely használata azért előnyös, mert megbízhatóbb kézbesítést és tisztább IP-reputációt tesz lehetővé, mint az ingyenes megoldások.&lt;/p&gt;&lt;h3&gt;Milyen gyakran kell megváltoztatnom a tárhely jelszavamat?&lt;/h3&gt;&lt;p&gt;A jelszót azonnal módosítani kell, ha felmerül a gyanú, hogy illetéktelenek kezébe került, egyébként a jelszókezelővel generált, komplex kódok használata a célravezető. A modern biztonsági ajánlások ma már nem a gyakori váltást, hanem a jelszó hosszát és egyediségét hangsúlyozzák. Ha legalább 16 karakteres, véletlenszerű karaktersort használ, elég évente egyszer frissítenie azt. A legfontosabb, hogy soha ne használja ugyanazt a belépési kódot több különböző webes felületen.&lt;/p&gt;&lt;h3&gt;Tényleg szükséges a kétfaktoros azonosítás minden fiókhoz?&lt;/h3&gt;&lt;p&gt;Igen, a kétfaktoros azonosítás (2FA) használata elengedhetetlen, mivel ez jelenti a leghatékonyabb védelmi vonalat a jelszólopással szemben. Még ha egy támadó meg is szerzi a belépési adatait, a telefonjára érkező kód nélkül nem tud bejutni a rendszerbe. Ez a megoldás drasztikusan növeli a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; szintjét, és minimálisra csökkenti az automatizált feltörési kísérletek sikerességét. Minden üzleti szempontból kritikus fióknál kötelező elemként kell tekinteni rá.&lt;/p&gt;&lt;h3&gt;Mi a különbség az ingyenes és a fizetős SSL tanúsítvány között?&lt;/h3&gt;&lt;p&gt;Az ingyenes és a fizetős SSL tanúsítványok ugyanazt a titkosítási szintet nyújtják, a különbség a hitelesítés mélységében és a kártérítési garanciában rejlik. Az ingyenes megoldások, mint a Let’s Encrypt, 90 naponta frissülnek, és csak a domain tulajdonjogát igazolják. A fizetős tanúsítványok viszont szervezeti hitelesítést is kínálhatnak, ami nagyobb bizalmat ébreszt az ügyfelekben. Emellett a fizetős verziókhoz pénzügyi garanciavállalás is tartozik, ami egy esetleges biztonsági incidens során anyagi védelmet nyújthat a vállalkozásnak.&lt;/p&gt;&lt;h3&gt;Hogyan ellenőrizhetem, hogy feltörték-e a tárhely fiókomat?&lt;/h3&gt;&lt;p&gt;A feltörés jele lehet a weboldal váratlan lassulása, ismeretlen fájlok megjelenése vagy a keresőmotorok által küldött biztonsági figyelmeztetés. Érdemes rendszeresen ellenőrizni a cPanel naplóit, ahol láthatja, ha valaki ismeretlen IP-címről vagy idegen országból próbált belépni a fiókba. Ha a látogatókat gyanús, idegen oldalakra irányítja át a böngészőjük, vagy ha a tárhely erőforrás-használata hirtelen megugrik, nagy valószínűséggel kártékony kód fut a rendszerében, ami azonnali technikai auditot igényel.&lt;/p&gt;&lt;h3&gt;Mit tegyek, ha gyanús tevékenységet észlelek az emaileimnél?&lt;/h3&gt;&lt;p&gt;Gyanús email tevékenység esetén az első lépés a jelszó azonnali megváltoztatása és a kétfaktoros azonosítás bekapcsolása. Vizsgálja felül az üzenetküldési szabályokat és a továbbítási beállításokat, hogy ne kerüljenek másolatok idegen címekre. Ellenőrizze az elküldött levelek mappáját, és ha ismeretlen üzeneteket talál, értesítse a partnereit a lehetséges visszaélésről. Vegye fel a kapcsolatot a szolgáltató ügyfélszolgálatával is, hogy segítsenek a belépési naplók elemzésében és a fiók teljes körű megtisztításában.&lt;/p&gt;&lt;h3&gt;Hogyan segít a cPanel a tárhelyem védelmében?&lt;/h3&gt;&lt;p&gt;A cPanel számos beépített eszközzel segíti a &lt;strong&gt;tárhely fiók biztonság&lt;/strong&gt; fenntartását, ilyen például az IP-blokkoló és a ModSecurity tűzfal. Ezekkel a funkciókkal szűrhetők a rosszindulatú kérések és korlátozható a hozzáférés a kritikus fájlokhoz. A felületen egyszerűen kezelhetők az SSL tanúsítványok és a kétlépcsős azonosítás is, így technikai mélyvíz nélkül is professzionális védelmet állíthat be. A rendszer segít abban is, hogy folyamatosan monitorozza a fájlok épségét és a hálózati kéréseket.&lt;/p&gt;&lt;h3&gt;Mennyire biztonságosak az aWh szerverei a támadásokkal szemben?&lt;/h3&gt;&lt;p&gt;Az aWh szerverei többrétegű védelmi rendszerrel rendelkeznek, amely magában foglalja a hardveres DDoS védelmet és a folyamatos szerveroldali monitorozást. Infrastruktúránkat modern NVMe tárolókra és naprakész szoftveres környezetre építettük a maximális biztonság érdekében. Több mint 10 éves tapasztalatunkkal folyamatosan auditáljuk rendszereinket, így ügyfeleink adatai védve vannak a támadásokkal szemben. A magyar nyelvű szakértői támogatásunk pedig bármilyen incidens esetén gyors segítséget garantál, biztosítva az üzleti folytonosságot és a nyugodt munkavégzést.&lt;/p&gt;&lt;span&gt;2026-08-22&lt;/span&gt;</content:encoded>
</item>
<item>
<title>Github + Hugo + Vercel</title>
<link>https://blog.3qin.us/github_hugo_vercel.html</link>
<enclosure type="image/jpeg" length="0" url="https://mail.3qin.us/~derek/blog_imgs/pics/vercel_with_github.png"></enclosure>
<guid isPermaLink="false">Wxzl9lI7ODtC-AUKvFEJg7xEGASw6CxIEn-sTA==</guid>
<pubDate>Sat, 29 Aug 2026 16:38:00 +0000</pubDate>
<description>我 上一篇博客 介绍了几种托管一个小型静态网站的方法。本文专注于一种目前我认为的最佳配置，详细介绍如何做一个个人博客，确保资料安全，保护访客的个人隐私，没有人打广告，或者删你的帖，而且完全免费。概括而言：</description>
<content:encoded>&lt;p&gt;我&lt;a href=&quot;https://blog.3qin.us/static_hosting.html&quot;&gt;上一篇博客&lt;/a&gt;介绍了几种托管一个小型静态网站的方法。本文专注于一种目前我认为的最佳配置，详细介绍如何做一个个人博客，确保资料安全，保护访客的个人隐私，没有人打广告，或者删你的帖，而且完全免费。概括而言：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;你在本机使用文本编辑器用&lt;a href=&quot;https://guides.github.com/features/mastering-markdown/&quot;&gt;markdown&lt;/a&gt; 格式写作文章&lt;/li&gt;
&lt;li&gt;你的博客使用&lt;a href=&quot;https://github.com&quot;&gt;Github&lt;/a&gt;来做源代码管理，确保一切历史得到妥善保护&lt;/li&gt;
&lt;li&gt;然后通过&lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt; 自动生成发布并托管&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;本地工具安装&lt;/h2&gt;&lt;p&gt;首先确保你本地有需要的工具。&lt;/p&gt;&lt;h3&gt;字符终端&lt;/h3&gt;&lt;p&gt;某些朋友对字符终端有畏惧心理。但是，字符终端是最好的教学工具，它可以让你用最小的代价来传达一步步的指导。敲什么命令，预期什么样的返回，不会有任何模糊不清的情况。当然，这里命令行操作中的很大部分，甚至可能是全部，可以用图形界面操作来取代，但这是后话，等待你自己主动去发现。&lt;/p&gt;&lt;p&gt;Mac OSX 和 Linux 用户可以跳过这一段，你的电脑已经有可用的字符终端了。Windows用户请安装&lt;a href=&quot;https://www.msys2.org/&quot;&gt;msys2&lt;/a&gt;, 并按照首页指示操作直到 &lt;code&gt;pacman -S git&lt;/code&gt;&lt;/p&gt;&lt;h3&gt;Hugo&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;是我们要使用的静态网站生成器。静态网站生成器有很多种，Hugo既不是最简单易用的，也不是功能最强大的，但综合两方面考虑我郑重推荐Hugo。Hugo 使用&lt;a href=&quot;https://golang.org/&quot;&gt;Go 语言&lt;/a&gt;开发，但你今天甚至未来都不需要懂一点点 Go 语言，在未来你可能需要修改一些模版文件来定制化自己的博客。&lt;/p&gt;&lt;p&gt;严格来说，你甚至不需要安装运行Hugo就可以发布博客，这一点我后来会提到。Hugo的安装非常简单，下载后只有一个可执行文件，把这个可执行文件拷贝合适的地方，在命令行直接运行就是了。&lt;a href=&quot;https://gohugo.io/getting-started/installing/&quot;&gt;安装说明&lt;/a&gt; 有点长，但你只需按照文中第一种方法就好。&lt;/p&gt;&lt;h3&gt;文本编辑器&lt;/h3&gt;&lt;p&gt;文本编辑器我不愿意做推荐。任何像话的文本编辑器都支持markdown文件编辑，例如Notepad++, Sublime Text, Textmate, VS code, Emacs, VIM, … 通常而言软件工作者对文本编辑器都有很强的偏好，例如我，不能忍受使用任何非Emacs 工作超过十分钟。&lt;/p&gt;&lt;p&gt;如果你完全没有概念，也不知道可以用什么，可以尝试使用&lt;a href=&quot;https://atom.io/&quot;&gt;Atom&lt;/a&gt;. Atom 来自Github，可以和Git工作流程紧密结合，界面也比较新潮，对新用户相对友好。&lt;/p&gt;&lt;p&gt;补充：我现在建议初学者使用&lt;a href=&quot;https://www.zettlr.com/&quot;&gt;zettlr&lt;/a&gt;, 这是一个对Markdown 专门优化的编辑器，和Atom一样也是用跨平台的Electron框架写成。&lt;/p&gt;&lt;h2&gt;加入 Github&lt;/h2&gt;&lt;p&gt;你需要在&lt;a href=&quot;https://github.com/&quot;&gt;Github&lt;/a&gt;注册账户。这个过程是免费的，而且仅需要一个邮箱信息。你的代码库会存在这里。之后你和guthub的交互主要是通过命令行工具，其他图形界面工具或第三方工具完成，网页交互不会太多。注册完用户之后你需要开通SSH public key访问，Github&lt;a href=&quot;https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh&quot;&gt;这里&lt;/a&gt;有详细步骤，我这里不再详述了。Windows用户的本地git工具在上面步骤已经安装完成，Linux请使用本机git，OSX用户第一次使用git的时候命令行会有提示如何安装git。&lt;/p&gt;&lt;p&gt;你在github网站的操作暂时告一段落，之后本文所有操作都是在外部完成的。&lt;/p&gt;&lt;h2&gt;加入 Vercel&lt;/h2&gt;&lt;p&gt;其实Github本身就可以做发布，但今天我不介绍，因为有更好的方法。请到&lt;a href=&quot;https://vercel.com/&quot;&gt;Vercel&lt;/a&gt; 网站，无需注册新用户，直接点 &lt;code&gt;Continue with github&lt;/code&gt; 按钮：&lt;/p&gt;&lt;figure&gt;
&lt;img src=&quot;https://mail.3qin.us/~derek/blog_imgs/pics/vercel_with_github.png&quot; alt=&quot;continue with github&quot; title=&quot;&quot;/&gt;
&lt;figcaption&gt;continue with github&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;填写一些基本信息后，请点击 &lt;code&gt;import Template&lt;/code&gt; 按钮：&lt;/p&gt;&lt;figure&gt;
&lt;img src=&quot;https://mail.3qin.us/~derek/blog_imgs/pics/import_template.png&quot; alt=&quot;import Template&quot; title=&quot;&quot;/&gt;
&lt;figcaption&gt;import Template&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;然后注意选择hugo模版，排的不是太靠前，注意不要选错了。之后请一切照提示，把模版安装到github，并做首次发布，一个空白的，完全自主的博客就上线了。&lt;/p&gt;&lt;h2&gt;如何加入自己的内容&lt;/h2&gt;&lt;p&gt;首先，为简化起见，你的博客上自己提供的内容只有两种：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;文章，就是 &lt;code&gt;.md&lt;/code&gt; 文本文件&lt;/li&gt;
&lt;li&gt;图片，可以是 &lt;code&gt;.png&lt;/code&gt; 或 &lt;code&gt;.jpg&lt;/code&gt; 文件，注意大小不要太大，最好不超过250KB，否则请自行缩小或降低分辨率。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;请使用git工具把生成的模版工程克隆到本地，Github上有明确指示。&lt;a href=&quot;https://gohugo.io/documentation/&quot;&gt;hugo 网站&lt;/a&gt;上有相当详细的文档，但你暂时不需要看。你需要干的几件事如下。&lt;/p&gt;&lt;h3&gt;配置&lt;/h3&gt;&lt;p&gt;工程根目录下有个极简的配置文件 &lt;code&gt;config.toml&lt;/code&gt;, 内容大致如下：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;baseURL = &amp;quot;https://xxx.xxx.xx/&amp;quot;
languageCode = &amp;quot;en-us&amp;quot;
title = &amp;quot;XXXX&amp;quot;
theme = &amp;quot;ananke&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;请把 &lt;code&gt;baseURL&lt;/code&gt; 改成自己需要的域名。没有域名的话，可以使用 Vercel 提供的临时域名，你在Vercel网站上有，形如 &lt;code&gt;xxx.vercel.app&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;请把 &lt;code&gt;title&lt;/code&gt; 改成自己想要的样子。没了。&lt;/p&gt;&lt;h3&gt;第一篇文章&lt;/h3&gt;&lt;p&gt;你的博客文章会放在 &lt;code&gt;content/posts&lt;/code&gt; 目录下，每篇文章是一个 &lt;code&gt;.md&lt;/code&gt; 文件。目前目录中只有有一篇基本空白的范例。文件分两部分，第一部分是一个简单的前言，包含本文的一些简单信息，例如：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;---
title: Send My Own Emails
slug: send_my_own_emails
date: 2020-08-06
tags:
- email
description: Sending your own email by yourself is suprisingly hard.
---&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;含义不言而寓。这个格式是一个简单的yaml格式。&lt;/p&gt;&lt;p&gt;之后就是你的文章了，一个较简洁的格式介绍在&lt;a href=&quot;https://guides.github.com/features/mastering-markdown/&quot;&gt;这里:&lt;/a&gt;&lt;/p&gt;&lt;p&gt;你的图片放在 &lt;code&gt;static&lt;/code&gt; 目录里，在你文章引用的时候请使用绝对路径, 但从&lt;code&gt;static&lt;/code&gt; 目录开始算。例如，如果图片存成: &lt;code&gt;static/pics/mypic.jpg&lt;/code&gt; 则网页引用路径是: &lt;code&gt;/pics/mypic.jpg&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Hugo 预览&lt;/h3&gt;&lt;p&gt;Hugo 包含本地动态预览功能。请在工程根目录下键入命令：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;hugo server&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后打开你的浏览器指向：&lt;code&gt;http://localhost:1313/&lt;/code&gt; 你就看到你的博客预览效果了。你用文本编辑器每次存盘浏览器都会自行刷新。&lt;/p&gt;&lt;h3&gt;发布&lt;/h3&gt;&lt;p&gt;发布其实和Hugo无关，而是通过git通知github，再从github通知vercel，vercel会自动从github抓取最新工程，自己生成再发布。&lt;/p&gt;&lt;p&gt;你需要做的是在git里加入你新增的文件，记住不要加入生成文件 (public/) ，然后 commit 再 push。&lt;/p&gt;&lt;pre&gt;&lt;code&gt;git add posts/*.md static/*.jpg static/*.png
git commit -a
git push&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;之后如果顺利的话，不到一两分钟，你的内容就上网了。有兴趣的话可以到&lt;code&gt;vercel&lt;/code&gt; 上观察过程日志。&lt;/p&gt;&lt;h3&gt;模版订制&lt;/h3&gt;&lt;p&gt;这一部分暂时超纲，你需要对hugo的模版机制有一定了解。模版文件都在 &lt;code&gt;themes/THEME&lt;/code&gt; 目录下，其中THEME是你配置文件中配置的。如果没改过的话是&lt;code&gt;ananke&lt;/code&gt;. 有兴趣的话可以进去看看。&lt;/p&gt;&lt;p&gt;还有一种可能是从&lt;a href=&quot;https://themes.gohugo.io/tags/blog/&quot;&gt;网上&lt;/a&gt; 下载全新的theme，目前有几百个之多。&lt;/p&gt;&lt;h2&gt;结束语&lt;/h2&gt;&lt;p&gt;你的自主博客之路才刚刚开始，还有很多值得你去探索。别的好处我暂时不谈，这里我谈一谈资料安全性。在本文介绍的方法下，每一个部分都可以更换：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Vercel不能用了还有别的托管公司，例如&lt;a href=&quot;https://netlify.com&quot;&gt;Netlify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Github不能用了还有Gitlab，Bitbucket, …&lt;/li&gt;
&lt;li&gt;Hugo不能用了还有很多很多静态网站生成器，都支持markdown，最多前言部分需要小修改&lt;/li&gt;
&lt;li&gt;你电脑坏了，你所有的文章，图片，包括所有历史在Github有备份&lt;/li&gt;
&lt;li&gt;如果网络都没有了，你所有的文章，图片，包括所有历史都在本地有备份&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;从数据量来说，你的文章微不足道。从意义来说，除了对你自己，对别人的意义未必很大。但是这是你自己的言论自由，什么人都管不着。&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;荣誉，隐私，言论自由。假如你自己都不在乎，不会有人在乎你的。– 周溱&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded>
</item>
<item>
<title>State of the blog – 2024. – S-Config</title>
<link>https://www.s-config.com/state-of-the-blog-2024/</link>
<enclosure type="image/jpeg" length="0" url="https://www.s-config.com/core/wp-content/uploads/2024/01/Liminal-Space-001-Title.jpg"></enclosure>
<guid isPermaLink="false">GPysnrHDoGd7Q6DGqTNNlIIXnoScYRmXFU_4lA==</guid>
<pubDate>Sat, 29 Aug 2026 01:32:02 +0000</pubDate>
<description>Although late to the party. Here&#39;s to 2024! This New Year&#39;s Eve we spent at home and by ourselves. It was super uneventful really.  Didn&#39;t go to the Barcade like the year prior. Nor did we go to the lakeshore. Or to a warehouse listening to speed metal cooking 36lbs of meat in a 55-gallon drum whil</description>
<content:encoded>&lt;h2&gt;Although late to the party. Here&amp;#39;s to 2024!&lt;/h2&gt;&lt;p&gt;This New Year&amp;#39;s Eve we spent at home and by ourselves. It was super uneventful really.  Didn&amp;#39;t go to the Barcade like &lt;a href=&quot;https://www.s-config.com/state-of-the-blog-2023/&quot;&gt;the year prior&lt;/a&gt;. Nor did we go to the lakeshore. Or to a warehouse listening to speed metal cooking 36lbs of meat in a 55-gallon drum while only having a single packet of hamburger buns sitting on the makeshift picnic table looking incredibly sad for most of the night.&lt;/p&gt;&lt;p&gt;But hey, let&amp;#39;s review what all happened this year, shall we?&lt;/p&gt;&lt;h3&gt;Events in 2023.&lt;/h3&gt;&lt;p&gt;We went to a few events that are within the Midwest region, and the attendance numbers of these events were rather large. However, we will not be talking about these events directly as it goes against a personal rule that we&amp;#39;ve set up for blogging here. That rule is&lt;/p&gt;&lt;p&gt;&amp;quot;No convention talk&amp;quot;&lt;/p&gt;&lt;p&gt;Why? Because Fandom is fanatical. Criticism even if it&amp;#39;s constructive will be met with violence because there is nothing wrong in the mind of the delusional.&lt;/p&gt;&lt;h4&gt;Furry events:&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://www.s-config.com/the-furry-in-s/&quot;&gt;Furry in us all.&lt;/a&gt;&lt;a href=&quot;https://www.s-config.com/the-furry-in-s/&quot;&gt;&lt;img src=&quot;https://www.s-config.com/core/wp-content/uploads/2016/11/Furry-in-us-all-Title.jpg&quot; alt=&quot;Furry In Us All.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;To what has been mentioned above. Back in 2016, we wrote up this article about being furry and how no one leaves because there is no such thing as &lt;em&gt;&lt;strong&gt;&amp;#39;goodbye&amp;#39;&lt;/strong&gt;&lt;/em&gt; which is why instead people simply fade from a fandom. Also, how things aren&amp;#39;t what they once were when it comes to communities. Occasionally we try to go to events to prove this article wrong.&lt;/p&gt;&lt;p&gt;In 2023, It&amp;#39;s as solid as ever. I&amp;#39;m just going to say that the event in question does not need my money. Their attendance will be perfectly fine until it isn&amp;#39;t. When that event cross that threshold it will be too late AND not my problem! Okay! moving on!&lt;/p&gt;&lt;h4&gt;Gaming events:&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://www.s-config.com/core/wp-content/uploads/2024/01/midwest-gaming-001.jpg&quot;&gt;&lt;img src=&quot;https://www.s-config.com/core/wp-content/uploads/2024/01/midwest-gaming-001.jpg&quot; alt=&quot;Midwest Gaming.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;Do avoid getting all debbie downer in 2023; If there is one thing we did enjoy in 2023 is going to &lt;a href=&quot;https://www.midwestgamingclassic.com/&quot;&gt;Midwest Gaming Classic&lt;/a&gt; which typically happens around April. I like this picture because of the Ouya. Now, there will be some readers that are going to explode on us with the following:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;You just said you&amp;#39;re not talking about conventions!! You asshole S!&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;If you go over to their &lt;a href=&quot;https://www.midwestgamingclassic.com/&quot;&gt;website&lt;/a&gt; the very first sentence on their homepage tells you they are not a convention. But a Trade Show. To some, this is splitting hairs. But there&amp;#39;s a distinct difference.&lt;/p&gt;&lt;p&gt;Trade shows typically are events set to encourage businesses to buy products. Trade Shows typically are more commercial than a convention. However, Midwest Gaming Classic kind of goes a little above and beyond with hosting panels at their Trade Show which is not too common. They&amp;#39;ve even reached out to the roleplaying and board game community too.&lt;/p&gt;&lt;p&gt;In the pinball world, the convention fits this category perfectly. Stern, American, and Jersey Jack all come down with their latest machines and take orders from bars restaurants, and collectors to have that new pinball machine delivered to their home. Sometimes buying the machine right off the convention floor and into their home.&lt;/p&gt;&lt;p&gt;I also posted a link on Odysee for the &lt;a href=&quot;https://odysee.com/@S-Config:1/Midwest-Gaming-Classic-2022:c&quot;&gt;2022 expo-hall walkthrough&lt;/a&gt; for those who want to see that. It&amp;#39;s over there mostly because we were testing &lt;a href=&quot;https://www.s-config.com/lbry-and-odysee-a-video-hosting-review/&quot;&gt;Odysee&lt;/a&gt; in a previous article.&lt;/p&gt;&lt;p&gt;Unlike a convention. A trade show feels super neutral. There are fans of certain products there of course! But it&amp;#39;s not to the level where you feel you&amp;#39;re being alienated from going.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.s-config.com/core/wp-content/uploads/2024/01/midwest-gaming-002.jpg&quot;&gt;&lt;img src=&quot;https://www.s-config.com/core/wp-content/uploads/2024/01/midwest-gaming-002.jpg&quot; alt=&quot;Midwest Gaming Classic - Price of physical.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;Never thought we&amp;#39;d see the day of a game having a $2,199 price tag on it.&lt;/p&gt;&lt;p&gt;The picture above is just one vendor in the hall and there are so many styles of vendors it ranks from the &lt;em&gt;&lt;strong&gt;&amp;quot;I know what I got&amp;quot;&lt;/strong&gt;&lt;/em&gt; vendor like the picture above. To the &lt;em&gt;&lt;strong&gt;&amp;quot;What&amp;#39;s in the box? I dunno. It&amp;#39;s yours for $10! I need this out of my attic!&amp;quot;&lt;/strong&gt;&lt;/em&gt; which are the vendors we like. Thank god for emulators where people can maintain their unopened boxes while the price goes to the moon right?&lt;/p&gt;&lt;p&gt;Anyhow, it was good last year. I&amp;#39;ll probably go back this year. Also, this isn&amp;#39;t the only gig in the world. There are a few pinball and arcade shows like this throughout the states and each of them of course has its vibe to it. So if you do have one in your city. It&amp;#39;s worth checking out at least once.&lt;/p&gt;&lt;p&gt;How will you find me at Midwest Gaming Classic? I&amp;#39;ll be one of those nerdy types, with a video game shirt. Walking up and down the halls.&lt;/p&gt;&lt;p&gt;Glad we narrowed it down for you.&lt;/p&gt;&lt;h3&gt;Best 2023 article.&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.s-config.com/intel-arc-a380-does-it-encode/&quot;&gt;&lt;img src=&quot;https://www.s-config.com/core/wp-content/uploads/2023/03/Intel-A380-ARC-Title.jpg&quot; alt=&quot;Intel A380 arc title.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I&amp;#39;d say one of my best articles for 2023 is the &lt;a href=&quot;https://www.s-config.com/intel-arc-a380-does-it-encode/&quot;&gt;Intel Arc A380 video encoding demo&lt;/a&gt;. I should probably revisit that and drop that card into a box with a legit bridge to use it correctly. But it&amp;#39;s great because we&amp;#39;re entering the world of GPT-generated content on the internet and We don&amp;#39;t want it to stop. We want it to get worse! So much worse that Google can&amp;#39;t even function anymore. Turn all of the major search engines into grey fucking goo for all we care! Because of how we do our blog entries. How can an AI not only perform the tests we did in this blog but also show the video result using video samples? Now, we&amp;#39;re sure someone with enough GPT could generate a workflow to do all of this. But most of these guys want easy money. Which is to use a cookie-cutter GPT to make an article that &amp;#39;copies&amp;#39; other people and moves on.&lt;/p&gt;&lt;h3&gt;The website itself.&lt;/h3&gt;&lt;p&gt;We&amp;#39;ve both improved and regressed at the same time. That is, our site is moving faster because we got rid of all Javascript. By getting rid of all Javascript our website may also feel a little dated.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://www.s-config.com/core/wp-content/uploads/2024/01/Marginalia-Search-Results.png&quot; alt=&quot;Marginalia search for this site.&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Respectfully on &lt;a href=&quot;https://search.marginalia.nu/&quot;&gt;Marginalia&amp;#39;s search engine&lt;/a&gt; &lt;em&gt;(one of those few clear-near search engines we legitimately respect because they deal with small hosting content instead of going for the conglomerates right away.)&lt;/em&gt; Those Javascript warnings are going away slowly. We suspect by the end of next year most of the ones that are still warning of javascript will be cleaned up too because of how cache settings on this site are set yearly for static page content.&lt;/p&gt;&lt;h3&gt;Webrings.&lt;/h3&gt;&lt;p&gt;They were too unwieldy to keep in the cellar door. Too many change requests. So we put webrings into a new LNK section &lt;a href=&quot;https://www.s-config.com/webrings/&quot;&gt;over here&lt;/a&gt;. Looking back at Webrings we question its use throughout 2023. Because when you think of webrings back in the day it was focused on a particular subject. Is putting a button on a page enough for the reader to click on it? Or is it better to stick with my &lt;a href=&quot;https://www.s-config.com/cellar-door/&quot;&gt;cellar door&lt;/a&gt; methodology of promotion where we write a paragraph as to WHY it exists and WHY it&amp;#39;s respected? Then, the reader can decide after explaining a graphic/link its existence if this is someplace they want to go. If the experiment gets too stupid I&amp;#39;ll pull the plug and reconsolidate each webring network as a single mention instead of going image-spam crazy.&lt;/p&gt;&lt;h4&gt;The enimic Tor / I2P&lt;/h4&gt;&lt;p&gt;This might be changed a lot quicker. Lainchan attempted to include these addresses but they don&amp;#39;t seem to be replicating all that well. I might revert to my classic listing structure where it&amp;#39;s just a site that I have found and have worked on throughout the years.&lt;/p&gt;&lt;h3&gt;Mastodon.&lt;/h3&gt;&lt;p&gt;The experiment is still ongoing on &lt;a href=&quot;https://core.s-config.com&quot;&gt;core&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://www.s-config.com/core/wp-content/uploads/2024/01/S-Configs-Pleroma-Instance.jpg&quot; alt=&quot;S-Config&amp;#39;s Pleroma Instance.&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;The timeline was 401&amp;#39;ed to prevent a bunch of scabs from indexing anything here. But it&amp;#39;s still online for me to use. Outside of the technical hells of updating it from time to time, it&amp;#39;s been far less stressful for me to be on Pleroma/Mastodon than it is on Twitter. In a sense, because we&amp;#39;re using the Pleroma engine to hook up to Mastodon it sort of leaves our instance in the reaver territory of the fediverse where some of the bigger ones would ignore any contribution I may have for their timeline. However, in the past being that we were on the primary mastodon instance we think this is a good thing to be excluded from that level of crap.&lt;/p&gt;&lt;p&gt;Of course, clicking &lt;a href=&quot;https://core.s-config.com/about&quot;&gt;about&lt;/a&gt; you see lots of love for Threads attempting to join my instance. But overall. It&amp;#39;s been a good experience because social networks like twitter/X TURNED into television. With ads being everywhere and people you don&amp;#39;t know telling you how you should live your life. To which they deserve all of the bags of dicks to chew on.&lt;/p&gt;&lt;h3&gt;Final thoughts.&lt;/h3&gt;&lt;p&gt;looking at 2023, we did fail on a lot of things we wanted to do. We wanted to do more art. Maybe even a comic. Get some 3-d shit done. But you see. When you&amp;#39;re adulting hard through life. RL has a way of destroying all of the things that you want to do. Perhaps it&amp;#39;s being too much of a workaholic to focus on anything really fun. Or living in a world where the possibility of not being under the thumb of a landlord that jacks your rent by forty percent is turning savings and ownership into a laughable dream because you exchange that thumb of oppression with another thumb which is a supposed bank with a 30-year mortgage. Fun fact, a life sentence in jail is 25 years.&lt;/p&gt;&lt;p&gt;Of course, you get those that argue that the debt is &amp;#39;good&amp;#39;. Just like college debt is &amp;#39;good&amp;#39; because the education helps further your career and ability to get jobs. For every person, we know where that is true. there are 20 that have gone through college, have received their degrees, and are essentially back at working for Home Depot this time with 40-120k of debt over their head. Now, those who are working in the careers required for college are just barely breaking even. Living check to check. So even when you do get the career you want. Throw the wrench of inflation into the mix and now you spent 4 years being back where you started.&lt;/p&gt;&lt;p&gt;So what is a person to do? Well, since we&amp;#39;re all not stacking mad eddies in this cyberpunk dystopia making six-figure incomes every year. Does one decide to save up just enough for a foreclosed house and live the fight club dream while trying to slowly rebuild the place from the inside out? Even when that&amp;#39;s accomplished the average homeowner is still dancing madly upon the lip of a volcano because all it takes is a serious medical bill or an accident. And the house as an asset can be liquidated. Because if they see a run-down shack for a house they may decide it&amp;#39;s not even worth the legal hassle to pull the asset.&lt;/p&gt;&lt;p&gt;These are some of the stresses we think about. We know we&amp;#39;re not alone on this. Many are going through the same shit. Life was never given the &amp;#39;easy&amp;#39; button but damn a little bit of a break can go a long way in any generation.&lt;/p&gt;&lt;p&gt;Anyhow, that&amp;#39;s what server said&lt;/p&gt;&lt;p&gt;END OF LINE+++&lt;/p&gt;&lt;p&gt; &lt;/p&gt;</content:encoded>
</item>
<item>
<title>htmx 4.0.0 has been released! ~ htmx</title>
<link>https://four.htmx.org/announcements/2026-08-28-htmx-4.0.0-is-released</link>
<enclosure type="image/jpeg" length="0" url="https://four.htmx.org/img/og-image.png"></enclosure>
<guid isPermaLink="false">laQo6vFRPcCGu0z5iOsqQQrBRj4wsC4dHZYVZg==</guid>
<pubDate>Fri, 28 Aug 2026 19:30:48 +0000</pubDate>
<description>Release announcement, 2026-08-28.</description>
<content:encoded>&lt;h2&gt;htmx 4.0.0 Release&lt;/h2&gt;&lt;p&gt;The htmx team is very happy to announce the release of htmx 4.0.0!  This is the culmination of 8 months of work (plus
a &lt;a href=&quot;https://four.htmx.org/game&quot;&gt;game&lt;/a&gt;) and we are very happy with the results.&lt;/p&gt;&lt;p&gt;The idea of htmx 4 started to germinate when I decided to create &lt;a href=&quot;https://fixiproject.org&quot;&gt;fixi&lt;/a&gt; and, in doing so, got
more familiar with the &lt;code&gt;fetch()&lt;/code&gt; API and async programming in JavaScript.  (htmx had always
used &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest&quot;&gt;XMLHttpRequest&lt;/a&gt; due
to backwards compatibility issues.)&lt;/p&gt;&lt;p&gt;One chance evening I was contacted by Christian, who had some interesting ideas
around &lt;a href=&quot;https://four.htmx.org/docs#streaming-html&quot;&gt;streaming HTML&lt;/a&gt; that
got me thinking that moving the internals to &lt;code&gt;fetch()&lt;/code&gt; would simplify things for him and for the library in general.&lt;br/&gt;
After a bit of work I managed to get Michael and Alex on board, and we were off to the races.&lt;/p&gt;&lt;p&gt;Development has been very smooth. We started a port of fixi + the htmx test suite. Over time, we rediscovered why htmx
did many of the things that it did and moved our new implementation closer and closer to the old one. At this point
the behavioral differences between 2.x and 4.x are relatively small and where they do diverge we have made explicit
choices that we feel will put htmx-based applications in a good spot for
being &lt;a href=&quot;https://www.youtube.com/watch?v=lASLZ9TgXyc&quot;&gt;100-year web services&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Note that we are not marking 4.0 as &lt;a href=&quot;https://docs.npmjs.com/cli/v10/commands/npm-dist-tag#purpose&quot;&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/a&gt; in NPM
because we do not want to force-upgrade users who are relying on non-versioned CDN URLs for htmx. Instead, 2.x will
remain &lt;code&gt;latest&lt;/code&gt; and the 4.0 line will remain &lt;code&gt;next&lt;/code&gt; until some point in early 2027. The website, however, will reference 4.0.&lt;/p&gt;&lt;h3&gt;Major Changes&lt;/h3&gt;&lt;p&gt;As mentioned above, htmx 4, from a user’s viewpoint, is almost identical to htmx 2. There are three major changes:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Attribute inheritance is now explicit by default rather than implicit by default (this is the biggest upgrade item)&lt;/li&gt;
&lt;li&gt;The htmx event names have been standardized &amp;amp; cleaned up. Some advanced users may need to change the events they
listen for.&lt;/li&gt;
&lt;li&gt;History support now does not use &lt;code&gt;localStorage&lt;/code&gt; by default (which was a cause of many support headaches). Most people
won’t notice this at all.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Internally, we migrated from &lt;code&gt;XMLHttpRequest&lt;/code&gt; to &lt;code&gt;fetch()&lt;/code&gt; but that should be transparent for most users of htmx.&lt;/p&gt;&lt;h4&gt;Attribute Inheritance&lt;/h4&gt;&lt;p&gt;In htmx 2 many attributes were “inherited” by default. This allows you to place attributes on parent elements and
their behavior will apply to child elements. This behavior, which came from
the &lt;a href=&quot;https://intercoolerjs.org&quot;&gt;intercooler.js&lt;/a&gt; days, was inspired by CSS and, unsurprisingly, worked out about the same
as CSS: powerful but difficult to understand at times.&lt;/p&gt;&lt;p&gt;In htmx 4 attributes are not inherited unless you &lt;a href=&quot;https://four.htmx.org/docs#attribute-inheritance&quot;&gt;explicitly say so&lt;/a&gt; by adding an
&lt;code&gt;:inherited&lt;/code&gt; after the attribute name:&lt;/p&gt;&lt;p&gt;This will be the largest upgrade burden in migrating from htmx 2 to htmx 4. To make things easier, we have provided
a &lt;a href=&quot;https://four.htmx.org/docs#upgrade-checker&quot;&gt;command line tool&lt;/a&gt; to find places you need to mark as inherited.&lt;/p&gt;&lt;p&gt;Note that attributes like &lt;code&gt;hx-disinherit&lt;/code&gt;, etc. are no longer required and should be removed.&lt;/p&gt;&lt;h4&gt;Events&lt;/h4&gt;&lt;p&gt;The events triggered by htmx 2 had grown organically over the life of the library and were not particularly well
organized, making it difficult to know exactly which event was fired when.&lt;/p&gt;&lt;p&gt;In htmx 4, all events now follow &lt;code&gt;htmx:phase:action[:sub-action]&lt;/code&gt;:&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;htmx 2&lt;/th&gt;&lt;th&gt;htmx 4&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;htmx:beforeRequest&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;htmx:before:request&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;htmx:afterRequest&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;htmx:after:request&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;htmx:beforeSwap&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;htmx:before:swap&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;htmx:afterSwap&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;htmx:after:swap&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;htmx:configRequest&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;htmx:config:request&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;In addition, the following changes were made:&lt;/p&gt;&lt;p&gt;The full table is in &lt;a href=&quot;https://four.htmx.org/docs/whats-new-in-htmx-4&quot;&gt;What’s New in htmx 4&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The command line upgrade checker flags old event names in &lt;code&gt;hx-on&lt;/code&gt; attributes and in your JavaScript where it can find
them.&lt;/p&gt;&lt;h4&gt;History&lt;/h4&gt;&lt;p&gt;History support has always been included in htmx, allowing you to implement back-button aware actions with simple
attributes. In htmx 2, a cache in &lt;code&gt;localStorage&lt;/code&gt; was used to snapshot pages for restoration. Unfortunately a large
source of issues was that this snapshot could include DOM mutations by 3rd party JavaScript libraries. When the page was
restored, those mutations remained but the underlying JavaScript logic was not.&lt;/p&gt;&lt;p&gt;htmx 4 does not cache pages in &lt;code&gt;localStorage&lt;/code&gt;. On back navigation htmx re-fetches the page and swaps it into &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;,
or into the &lt;code&gt;[hx-history-elt]&lt;/code&gt; element if one is present. This allows 3rd party JavaScript libraries to “just work” in
most cases and, with good request caching, is very fast.&lt;/p&gt;&lt;p&gt;If you want local caching instead, we now ship a very complete &lt;a href=&quot;https://four.htmx.org/extensions/hx-history-cache&quot;&gt;&lt;code&gt;hx-history-cache&lt;/code&gt;&lt;/a&gt;
extension that restores history from &lt;code&gt;sessionStorage&lt;/code&gt; and is designed to integrate well with scripting solutions like
Alpine.js, etc.&lt;/p&gt;&lt;h3&gt;New Features&lt;/h3&gt;&lt;p&gt;There are two big new features in htmx 4, both of which we are really excited about:&lt;/p&gt;&lt;h4&gt;Morph Swaps&lt;/h4&gt;&lt;p&gt;We now support &lt;a href=&quot;https://four.htmx.org/docs#morphing-swaps&quot;&gt;morphing swaps&lt;/a&gt; out of the box with htmx.  I created &lt;a href=&quot;https://github.com/bigskysoftware/idiomorph&quot;&gt;idiomorph&lt;/a&gt; and
nearly included it in htmx 2.x but decided against it.  In htmx 4, Michael has done great work improving on that
algorithm and integrating it seamlessly into htmx.&lt;/p&gt;&lt;h4&gt;&lt;code&gt;&amp;lt;hx-partial&amp;gt;&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Another major new feature is the &lt;code&gt;&amp;lt;hx-partial&amp;gt;&lt;/code&gt; tag.  This tag is similar to &lt;a href=&quot;https://four.htmx.org/docs#out-of-band-swaps&quot;&gt;out-of-band swaps&lt;/a&gt;,
but is much clearer when you want to do something beyond just replacing a single element with a new version of itself:&lt;/p&gt;&lt;h4&gt;Extensions&lt;/h4&gt;&lt;p&gt;Much of the excitement in htmx 4 is in the extensions.  Switching to &lt;code&gt;fetch()&lt;/code&gt; internally let us rethink how extensions
can and should work, and sparked the creation (and recreation) of many new extensions, for example:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hx-preload&lt;/code&gt; - preload content (e.g. on &lt;code&gt;mouseover&lt;/code&gt;) to speed requests up&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hx-download&lt;/code&gt; - native, fetch-based file downloads&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hx-alpine-compat&lt;/code&gt; - smooths over compatibility issues between htmx and &lt;a href=&quot;https://alpinejs.dev/&quot;&gt;Alpine.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hx-history-cache&lt;/code&gt; - caches history in &lt;code&gt;sessionStorage&lt;/code&gt;, provides Alpine.js compatibility&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Additionally, there are three new or updated streaming HTML extensions:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://four.htmx.org/extensions/hx-sse&quot;&gt;&lt;code&gt;hx-sse&lt;/code&gt;&lt;/a&gt; streams over &lt;code&gt;text/event-stream&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://four.htmx.org/extensions/hx-ws&quot;&gt;&lt;code&gt;hx-ws&lt;/code&gt;&lt;/a&gt; streams and sends over WebSockets.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://four.htmx.org/extensions/hx-multipart&quot;&gt;&lt;code&gt;hx-multipart&lt;/code&gt;&lt;/a&gt; streams over &lt;code&gt;multipart/mixed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Finally, we decided it was time to try our hand at our own small front-end scripting solution that tightly integrates with htmx.  &lt;a href=&quot;https://four.htmx.org/extensions/hx-live&quot;&gt;&lt;code&gt;hx-live&lt;/code&gt;&lt;/a&gt; is inspired by Alpine.js, &lt;a href=&quot;https://jquery.com/&quot;&gt;jQuery&lt;/a&gt; and &lt;a href=&quot;https://hyperscript.org&quot;&gt;hyperscript&lt;/a&gt;, and makes front end scripting pleasant and fun.  It even supports what we are calling DOM-based, HATEOAS-friendly reactivity.&lt;/p&gt;&lt;p&gt;There is a new &lt;a href=&quot;https://four.htmx.org/docs/htmax&quot;&gt;&lt;code&gt;htmax.js&lt;/code&gt;&lt;/a&gt; bundle in the distribution which packages htmx with the most popular of these in a single file if you don’t want to think about which ones you want to pick.&lt;/p&gt;&lt;h3&gt;Upgrading&lt;/h3&gt;&lt;p&gt;For a complete upgrade guide see  &lt;a href=&quot;https://four.htmx.org/docs/whats-new-in-htmx-4&quot;&gt;What’s New in htmx 4&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;As mentioned earlier, we are providing an upgrade tool to help you:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ npx htmx.org@4.0.0 upgrade-check -- ./templates

File extensions: .html, .php, .js, .ts, .jinja, .jinja2, .j2, .erb, .hbs
Use --ext to add more (e.g. --ext .vue --ext .svelte)

Scanning 1 file(s)...

Found 8 issue(s) in 1 of 1 file(s).
templates/index.html:1: [inheritance] hx-headers needs :inherited suffix (descendant on line 3 has hx-delete) (this looks like a CSRF token; without :inherited the header does not reach child elements and the server rejects the request)
templates/index.html:2: [inheritance] hx-target needs :inherited suffix (descendant on line 3 has hx-delete)
templates/index.html:2: [inheritance] hx-confirm needs :inherited suffix (descendant on line 3 has hx-delete)
templates/index.html:3: [renamed-attr] hx-disable -&amp;gt; rename to hx-ignore (hx-disable now means &amp;#39;disable during request&amp;#39;)
templates/index.html:4: [removed-attr] hx-vars is removed -&amp;gt; use hx-vals with js: prefix
templates/index.html:4: [removed-attr] hx-prompt is removed -&amp;gt; load the hx-prompt extension to keep the same syntax
templates/index.html:9: [old-event] old event name &amp;quot;htmx:afterRequest&amp;quot; -&amp;gt; &amp;quot;htmx:after:request&amp;quot;
templates/index.html:9: [old-api] htmx.addClass() is removed -&amp;gt; use element.classList.add()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are also shipping an &lt;a href=&quot;https://four.htmx.org/docs/editor-support#ai-coding-assistants&quot;&gt;agent skill&lt;/a&gt; to assist in upgrading&lt;/p&gt;&lt;h3&gt;Installing&lt;/h3&gt;&lt;p&gt;htmx 4.0 can be installed via a package manager referencing version &lt;code&gt;4.0.0&lt;/code&gt;, or can be linked via a CDN:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&amp;quot;https://unpkg.com/htmx.org@4.0.0/dist/htmx.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;or &lt;a href=&quot;https://unpkg.com/htmx.org@4.0.0/dist/htmx.min.js&quot;&gt;Downloaded&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;LLMs&lt;/h3&gt;&lt;p&gt;Like it or not, a lot of people are using LLMs and we are providing the following skills files for LLMs:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://raw.githubusercontent.com/bigskysoftware/htmx/v4.0.0/dist/skills/htmx-guidance.md&quot;&gt;&lt;code&gt;htmx-guidance&lt;/code&gt;&lt;/a&gt; - core htmx skills for developing with htmx 4&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://raw.githubusercontent.com/bigskysoftware/htmx/v4.0.0/dist/skills/htmx-debugging.md&quot;&gt;&lt;code&gt;htmx-debugging&lt;/code&gt;&lt;/a&gt; - diagnosing htmx issues during dev&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://raw.githubusercontent.com/bigskysoftware/htmx/v4.0.0/dist/skills/htmx-extension-authoring.md&quot;&gt;&lt;code&gt;htmx-extension-authoring&lt;/code&gt;&lt;/a&gt; - writing and debugging htmx 4 extensions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://raw.githubusercontent.com/bigskysoftware/htmx/v4.0.0/dist/skills/htmx-upgrade-from-htmx2.md&quot;&gt;&lt;code&gt;htmx-upgrade-from-htmx2&lt;/code&gt;&lt;/a&gt; - migrating a codebase from htmx 2.x to 4.x&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;(Let’s leave aside if releasing a new version of a library in the LLM era is a good or bad thing!)&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;We hope you enjoy htmx 4.  htmx 2 will continue to be supported indefinitely so don’t feel any pressure to upgrade.&lt;/p&gt;&lt;p&gt;I’d like to thank the following people for all their help with this release:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MichaelWest22&quot;&gt;Michael West&lt;/a&gt; - Incredible teammate &amp;amp; grug-brained developer&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scriptogre&quot;&gt;Christian Tanul&lt;/a&gt; - Inspired htmx 4 &amp;amp; led the streaming &amp;amp; live extensions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alexpetros&quot;&gt;Alex Petros&lt;/a&gt; - For keeping the ship on an even keel&lt;/li&gt;
&lt;li&gt;Stephen Mitchell - The genius behind the &lt;a href=&quot;https://four.htmx.org/game&quot;&gt;game&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stu Kennedy - Our WebSockets expert&lt;/li&gt;
&lt;li&gt;André Ahlert Jr. - Providing IDE &amp;amp; Editor Support&lt;/li&gt;
&lt;li&gt;Dien Hoa Truong - For kicking the tires on early htmx 4 and helping fix many bugs&lt;/li&gt;
&lt;/ul&gt;&lt;h3&gt;Upgrade Music&lt;/h3&gt;&lt;p&gt;Wouldn’t be an htmx update without upgrade music:&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Nobody Argued For Your Stack - DEV Community</title>
<link>https://dev.to/playfulprogramming/nobody-argued-for-your-stack-51fj</link>
<enclosure type="image/jpeg" length="0" url="https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdt196dzugry1anlsqkvf.png"></enclosure>
<guid isPermaLink="false">AteDMWW1iiWUViK69neFZyVVzIxNwZqZKlpx_w==</guid>
<pubDate>Fri, 28 Aug 2026 19:30:48 +0000</pubDate>
<description>Last week, it came to light Cursor had mostly finished migrating from SolidJS to React. This...</description>
<content:encoded>&lt;p&gt;Last week, it came to light &lt;a href=&quot;https://x.com/poteto/status/2089227731305464150&quot;&gt;Cursor had mostly finished migrating from SolidJS to React&lt;/a&gt;. This migration happened about seven months ago. But it became a central focus of discussion following the &lt;a href=&quot;https://www.solidjs.com/blog/solid-2-0-rc-the-big-reveal&quot;&gt;Solid 2.0 RC release&lt;/a&gt;. Then yesterday, a week later, it came to my attention that the Anthropic docs example command for their large-scale migration feature is:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fypgfccjdqzhchq7q76h2.png&quot;&gt;&lt;img src=&quot;https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fypgfccjdqzhchq7q76h2.png&quot; alt=&quot; &quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I admit that my &lt;a href=&quot;https://x.com/RyanCarniato/status/2092631951530410280&quot;&gt;gut reaction&lt;/a&gt; was not great. Out of all the examples they could have chosen... Years of my work became a canonical example of the thing you migrate away from — in the same week we shipped the biggest release in the project&amp;#39;s history — stung in a way I won&amp;#39;t pretend it didn&amp;#39;t. My second reaction was to assume that, like the &lt;a href=&quot;https://bytes.dev/archives/513&quot;&gt;other trickle-down posts&lt;/a&gt; I&amp;#39;d seen this week, this rode the same week-old news cycle.&lt;/p&gt;&lt;p&gt;Then I checked the Internet Archive and realized this has been there &lt;a href=&quot;https://web.archive.org/web/20260412131455/https://support.claude.com/en/articles/14554000-claude-code-power-user-tips&quot;&gt;since at least April 2026&lt;/a&gt;. Four months before the Cursor story broke.  At this point, the whole public footprint was a mention of an experiment sandwiched between bigger updates in a &lt;a href=&quot;https://cursor.com/blog/scaling-agents&quot;&gt;Cursor blog post&lt;/a&gt; posted in January. The kind of thing that no one outside the industry would even really pick up on. No reasoning, no benchmarks, no argument.&lt;/p&gt;&lt;p&gt;Stop to think about what that means. I should be careful here because I can&amp;#39;t prove anyone at Anthropic ever read that Cursor post. Nobody can. Maybe a docs writer saw the experiment. Maybe Claude drafted its own example. But think it through. Either it traveled from a buried line in one company&amp;#39;s release notes into another company&amp;#39;s official docs, or it needed no origin at all. It was already assumed before any public migration existed.&lt;/p&gt;&lt;p&gt;Our industry has quietly started broadcasting conclusions where it used to transmit arguments. We couldn&amp;#39;t have picked a worse time, because — as I&amp;#39;ll get to — arguments are the only source that still matters.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;
  
  
  Why This Matters More Than It Used To
&lt;/h2&gt;&lt;p&gt;It would be fair to ask, hasn&amp;#39;t it always been like this? Teams cargo cult large players. Netflix or Facebook uses this predates AI by decades. Optics and politics have always beaten pure technical merit. The weight of Facebook definitely helped React&amp;#39;s early propagation.&lt;/p&gt;&lt;p&gt;But a narrative used to come with friction. It was always in the race, but it couldn&amp;#39;t outrun the argument. This has changed for two reasons:&lt;/p&gt;&lt;p&gt;First, execution cost has collapsed. &lt;a href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;Bun&amp;#39;s rewrite from Zig to Rust&lt;/a&gt; — about a million lines — was executed almost entirely by Claude agents in days, not months. And before you start thinking &amp;quot;lock-in&amp;quot;, going the opposite way, while not as easy, has never been easier. Migrating from Rust to Zig or React to Solid has never been easier.&lt;/p&gt;&lt;p&gt;That sounds like good news. And it is. When migrations took years of human effort, cost put a damper on fashion. Now execution is a lot cheaper in all directions. The only thing left is the reason. Which suggests, on the surface, these decisions should be rooted in technical merit. But when narrative is what sets the direction, you start to see how a line buried in documentation is worth an essay. &lt;/p&gt;&lt;p&gt;Second, verdict production has been industrialized. &lt;a href=&quot;https://x.com/jaredpalmer/status/2089463862085251372&quot;&gt;Cognition just migrated its marketing site from Astro to Next.js&lt;/a&gt;. A content site. Astro&amp;#39;s home turf, by near-universal consensus, including from people with no stake in the fight. The migration was performed by Devin itself and published as a case study: the agent made the changes, tested them, recorded its own verification runs. No repository, no before-and-after numbers, no benchmark. The output wasn&amp;#39;t the site. It was the story.&lt;/p&gt;&lt;p&gt;And believe me, there is no shortage of these stories. Migrations are now the demo genre of the agent industry. &lt;a href=&quot;https://x.com/theo&quot;&gt;Theo Browne&lt;/a&gt; was trying to tell me this a couple weeks back with some very good advice. But it hits a lot harder when you feel it firsthand. Migrations that demo flawlessly are, by definition, the ones moving toward what the agent writes best today. Marketing departments are generating &amp;quot;X → dominant library&amp;quot; verdicts at a rate organic engineering decisions never would.&lt;/p&gt;&lt;p&gt;So you see the tension. Stacks have never been more sensitive to circulating verdicts, and verdicts have never been produced faster or with less reasoning attached.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;
  
  
  What an Argument Looks Like
&lt;/h2&gt;&lt;p&gt;These aren&amp;#39;t unheard of. They are just becoming rarer.&lt;/p&gt;&lt;p&gt;When &lt;a href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;Bun moved from Zig to Rust&lt;/a&gt;, Jarred Sumner made the case in public, with receipts. He talked about bug classes and memory management issues. You can disagree with the argument. &lt;a href=&quot;https://andrewkelley.me/post/my-thoughts-bun-rust-rewrite.html&quot;&gt;People have&lt;/a&gt;, point by point, in public. That&amp;#39;s the value. An argument can be engaged, checked, narrowed, refuted, or strengthened. A verdict can only be repeated.&lt;/p&gt;&lt;p&gt;Or look at TanStack&amp;#39;s journey with React Server Components on tanstack.com. They spent the year almost arguing with themselves. When &lt;a href=&quot;https://tanstack.com/blog/react-server-components&quot;&gt;tanstack.com adopted React Server Components&lt;/a&gt;, they wrote it up and measured it. When they &lt;a href=&quot;https://tanstack.com/blog/we-stopped-using-rsc-on-tanstack-com&quot;&gt;stopped using them&lt;/a&gt;, they did the same: What changed, what replaced it, why the tradeoff flipped for their specific case — while TanStack Start went right on supporting RSC as an opt-in primitive for everyone else. The reversal post calling the decision &amp;quot;uneventful&amp;quot; is a tell. A public reversal on React&amp;#39;s flagship architecture, and it generated insight instead of heat. Because they delivered analysis instead of verdict.&lt;/p&gt;&lt;p&gt;That&amp;#39;s what evaluation entering the public record looks like. jQuery didn&amp;#39;t lose to React because a training set said so. It lost an argument, in public, on merit over several years. Every library you respect got where it is by winning that kind of fight. The flow of events I described earlier doesn&amp;#39;t hold fights. It holds direction, and compounds on it.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;
  
  
  The Argument Nobody Published
&lt;/h2&gt;&lt;p&gt;In all fairness, Cursor never issued a verdict. An experiment that &amp;quot;still needs careful review&amp;quot;, then &lt;a href=&quot;https://x.com/poteto/status/2089227731305464150&quot;&gt;a congratulatory post on the Solid 2.0 RC release&lt;/a&gt;. But that post still characterized signals as &amp;quot;perf footguns&amp;quot; and &amp;quot;accidental fan out&amp;quot; — no numbers, nothing to engage — and reached straight for the React Compiler as the cure. No bad faith required. The diagnosis and the cure just came from the same place.&lt;/p&gt;&lt;p&gt;When I tried to reconstruct the case Cursor could have made, it turned out to be interesting. Far more interesting than &amp;quot;perf footguns.&amp;quot;&lt;/p&gt;&lt;p&gt;The post obviously highlighted agents were &amp;quot;quite bad at writing good Solid code,&amp;quot; everything &amp;quot;ended up being accidentally tracked.&amp;quot; While my &amp;quot;Solid-brain&amp;quot; struggles a bit to imagine what they were doing to get there, I don&amp;#39;t doubt it for a second. An agent that has internalized React&amp;#39;s mental model will write Solid like React, and Solid written like React is bad Solid. I&amp;#39;ve also seen the reverse. The difference is when the agent fumbles React, the training data catches it. When it fumbles Solid, the training data shrugs. This is a pure numbers game.&lt;/p&gt;&lt;p&gt;But &lt;a href=&quot;https://x.com/poteto/status/2089227731305464150&quot;&gt;the same post&lt;/a&gt; contains a decision in the opposite direction. They&amp;#39;re also moving from Tailwind to StyleX. This is a migration against volume. What StyleX offers instead is that agent mistakes get caught. Styles are typed with deterministic merge order, so a hallucinated class is a build error.&lt;/p&gt;&lt;p&gt;Put side by side, one conclusion can be made: make agents&amp;#39; mistakes cheap. Two ways to achieve that. Volume and verification. React (with its compiler) wins on volume and arguably on verification. StyleX loses on volume and wins decisively on verification. I won&amp;#39;t pretend under this lens with Solid 1.0 this is a particularly comfortable place for signal-based reactivity. But it&amp;#39;s also, not coincidentally, where Solid 2.0&amp;#39;s design attention has gone.&lt;/p&gt;&lt;p&gt;That analysis, volume and verification as two axes, is concrete, checkable, and useful. It implies testable claims and gives every framework author a design target. It might also be wrong. I reconstructed it from a benchmark-free post and my own inferences. Cursor&amp;#39;s actual reasoning may be entirely different.&lt;/p&gt;&lt;p&gt;The StyleX decision is even more interesting. Agent-driven development can move against volume when verification wins. There is a second angle that small technologies can embrace. To me, this isn&amp;#39;t a new revelation, but one where practice confirms theory.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;
  
  
  What Happens If Nothing Changes
&lt;/h2&gt;&lt;p&gt;If you follow this thinking to its logical conclusion, outside of a few exceptions, each layer collapses on its most popular solution today. Frontend converges on React. Systems converges on Rust. Scripting converges on Python. Even the sites that are just pages converge on Next, fit be damned. That&amp;#39;s a monoculture, arrived at not by anyone deciding libraries on their merits, but by a system that can no longer distinguish &amp;quot;abundant in the training data&amp;quot; from &amp;quot;better.&amp;quot;&lt;/p&gt;&lt;p&gt;If you are a React fan, you should still be concerned. React was good because it had competition. Hooks arrived in a world where composition patterns were being explored. The React Compiler exists, in part, as an answer to the question signals-based frameworks spent years forcing: Why should the developer pay for re-rendering the world? React&amp;#39;s greatest strength has always been its ability to metabolize pressure from outside. A monoculture doesn&amp;#39;t just kill the alternatives. It kills the pressure. An ecosystem without pressure doesn&amp;#39;t stay good. It stays stuck.&lt;/p&gt;&lt;p&gt;There is a path back. Teams publish analysis, not verdict. Model vendors evaluate what they ship in public. Libraries learn from StyleX&amp;#39;s example and bank on verification. And the rest of us learn to distinguish output from argument before we amplify it. The last one we&amp;#39;ve been struggling with since the dawn of social media.&lt;/p&gt;&lt;p&gt;In the meantime, we can keep generating our rules files, llms.txt, docs over MCP, curated examples. We do all of it for Solid 2.0, and it helps a lot. But context is borrowed, not owned. It has to be injected into every session, by every tool, forever, while the inertia works against you for free.&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;
  
  
  Where Does the Next Idea Come From?
&lt;/h2&gt;&lt;p&gt;I want to leave you with a more probing thought. The question this all comes down to isn&amp;#39;t whether Solid survives. We&amp;#39;re fine. We are shipping the biggest release in our history, with a community that chose this framework on purpose and, more importantly, knows why.&lt;/p&gt;&lt;p&gt;The question is where the next idea comes from.&lt;/p&gt;&lt;p&gt;Every paradigm that has mattered started as something the establishment couldn&amp;#39;t write. React was mocked for almost a year over JSX. Markup in JavaScript? Are you insane? What about separation of concerns? If today&amp;#39;s machinery had existed in 2013, models trained on a web that was all jQuery and Backbone, docs canonizing migrations everyone was already making, verdicts circulating months ahead of their arguments, do you think it could have gotten past that stage?&lt;/p&gt;&lt;p&gt;It didn&amp;#39;t win because the defaults favored it. It won because people made a verifiable case in public. And I don&amp;#39;t think the mechanisms are gone. Migrations have never been cheaper, in every direction. The door out of any monoculture has never been more open. We just need to keep analysis as part of the conversation even as we distance ourselves from the implementation. That&amp;#39;s being informed. That&amp;#39;s taking responsibility.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Next.js fixes two critical RCE flaws, including a Windows-only attack</title>
<link>https://4sysops.com/archives/next-js-fixes-two-critical-rce-flaws-including-a-windows-only-attack/</link>
<guid isPermaLink="false">vx4TbE8oeaMDJBSS8nUtZVTXSYuISer66r6MaA==</guid>
<pubDate>Fri, 28 Aug 2026 12:13:08 +0000</pubDate>
<description>Vercel has patched two critical unauthenticated remote-code-execution vulnerabilities in Next.js, including a Windows-only path-traversal flaw rated CVSS 9.0 and an AVIF image-processing bug rated CVSS 9.5. Administrators should upgrade immediately: no workaround is available for affected Windows deployments, while AVIF-enabled sites may be exposed to malicious image files. Source</description>
<content:encoded>&lt;div&gt;&lt;img src=&quot;https://4sysops.com/wp-content/uploads/2026/08/next-js-fixes-two-critical-rce-flaws-including-a-windows-only-attack.gif&quot; alt=&quot;Next.js fixes two critical RCE flaws, including a Windows-only attack&quot; title=&quot;&quot;/&gt;&lt;/div&gt;Vercel has patched two critical unauthenticated remote-code-execution vulnerabilities in Next.js, including a Windows-only path-traversal flaw rated CVSS 9.0 and an AVIF image-processing bug rated CVSS 9.5. Administrators should upgrade immediately: no workaround is available for affected Windows deployments, while AVIF-enabled sites may be exposed to malicious image files. &lt;p&gt;&lt;a href=&quot;https://4sysops.com/archives/next-js-fixes-two-critical-rce-flaws-including-a-windows-only-attack/&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Claude Cowork adds a browser for web tasks—with new admin and login risks</title>
<link>https://4sysops.com/archives/claude-cowork-adds-a-browser-for-web-tasks-with-new-admin-and-login-risks/</link>
<guid isPermaLink="false">jtD8UKk_R7Vdnz62Gb9N8hqE7YBnSMXo32IVXQ==</guid>
<pubDate>Fri, 28 Aug 2026 12:13:08 +0000</pubDate>
<description>Claude Cowork can now open a separate browser inside the Claude Desktop app, allowing it to navigate websites, read pages, click links, fill forms, and collect data without an extension. The built-in browser is rolling out to Pro, Max, and Team users, while Enterprise administrators can manage it for their organizations. Source</description>
<content:encoded>&lt;div&gt;&lt;img src=&quot;https://4sysops.com/wp-content/uploads/2026/08/claude-cowork-adds-a-browser-for-web-tasks-with-new-admin-and-login-risks.jpg&quot; alt=&quot;Claude Cowork adds a browser for web tasks—with new admin and login risks&quot; title=&quot;&quot;/&gt;&lt;/div&gt;Claude Cowork can now open a separate browser inside the Claude Desktop app, allowing it to navigate websites, read pages, click links, fill forms, and collect data without an extension. The built-in browser is rolling out to Pro, Max, and Team users, while Enterprise administrators can manage it for their organizations. &lt;p&gt;&lt;a href=&quot;https://4sysops.com/archives/claude-cowork-adds-a-browser-for-web-tasks-with-new-admin-and-login-risks/&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>In defense of two-state theme toggles - Josh Collinsworth blog</title>
<link>https://joshcollinsworth.com/blog/in-defense-of-two-state-theme-toggles/</link>
<enclosure type="image/jpeg" length="0" url="https://joshcollinsworth.com/images/post_images/two-state.webp"></enclosure>
<guid isPermaLink="false">1-XBEVoSJozwQriPSwKesYHcuH5zU-9z47kEbQ==</guid>
<pubDate>Thu, 27 Aug 2026 06:13:09 +0000</pubDate>
<description>There&#39;s a lively debate going on over whether theme toggles should have two options (light/dark) or three, with an added &quot;system&quot; option. I maintain two-state toggles are better in most cases (though not all), and that the case for a third option rests mainly on problems most users don&#39;t actually have.</description>
<content:encoded>&lt;img src=&quot;https://joshcollinsworth.com/images/post_images/two-state.webp&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;h1&gt;In defense of two-state theme toggles&lt;/h1&gt;&lt;div&gt;&lt;b&gt;Published:&lt;/b&gt; August 21, 2026 &lt;br/&gt; &lt;b&gt;Last updated:&lt;/b&gt; August 22, 2026&lt;/div&gt;&lt;p&gt;In case you haven’t kept up on the most recent &lt;del&gt;bikeshedding&lt;/del&gt; discussion in frontend: there’s a lively debate going on over the merits of a simpler two-state theme toggle (light/dark), vs. a more explicit three-state toggle (with dark and light, plus a third “system” option), and whether the clarity of that third option is worth the additional complexity it introduces.&lt;/p&gt;&lt;p&gt;But let’s back up to where this discussion started, and talk a little bit more about the details and tradeoffs those two approaches, before I start weighing in on what I think is (and is not) ideal, and why.&lt;/p&gt;&lt;h2&gt;The origins of the debate&lt;/h2&gt;&lt;p&gt;A couple of weeks ago, Lea Verou published an excellent post titled &lt;a href=&quot;https://lea.verou.me/blog/2026/dark-mode-toggles/&quot;&gt;&lt;em&gt;Dark mode toggles: two states are enough&lt;/em&gt;&lt;/a&gt;. (Evidently, this discussion had been ongoing for a good while prior.)&lt;/p&gt;&lt;p&gt;The post is thorough and insightful. Dr. Verou lays out her case that a two-state is enough most of the time, &lt;strong&gt;in the case of a switch that’s part of the main site UI&lt;/strong&gt;. That last part’s important:&lt;/p&gt;&lt;div&gt;&lt;p&gt;In &lt;em&gt;most&lt;/em&gt; cases, users aren’t looking for that third “system” option, and aren’t likely to find it useful. They’re just looking for the light switch.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;There are, of course, exceptions. For example, in a dedicated settings page, where the mental model is less “what do I want right this second” and more “what do I want this app to be like in the future?” a three-state toggle makes more sense. Dr. Verou’s post makes this abundantly clear, and I tend to agree.&lt;/p&gt;&lt;p&gt;Aside from the binary vs. trinary discussion: I find Dr. Verou’s two-state implementation particularly clever. Under the hood, rather than explicitly toggling between “light” or “dark”, it toggles between falling back to the system setting, and the &lt;em&gt;opposite&lt;/em&gt; of the system setting.&lt;/p&gt;&lt;p&gt;That is: rather than saving either a light or dark theme preference permanently in &lt;code&gt;localStorage&lt;/code&gt;, Dr. Verou’s implementation stores your preference &lt;em&gt;only if that preference is different than your device setting&lt;/em&gt;. In that case, the site saves whatever theme that happens to be.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Otherwise&lt;/em&gt;—or should the user toggle the theme once again—rather than storing the newly selected theme (which now once again matches their OS preference), the saved preference is simply deleted, causing the site to once again default to the device preference. This is important, because a common criticism of binary light/dark controls is that they lock you into one or the other, with no way to get back to your system preference; this two-state toggle neatly avoids that lock-in.&lt;/p&gt;&lt;p&gt;A couple of other points made in the post:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;With a three-way toggle, at any given time, two of the three options will do the same thing&lt;/strong&gt;. This is a bit redundant, and creates at least &lt;em&gt;some&lt;/em&gt; complexity by offering users more choice than they might want (or even understand; more on that in a bit).&lt;/p&gt;&lt;/li&gt; &lt;li&gt;&lt;p&gt;&lt;strong&gt;This is not a one-size-fits-all implementation&lt;/strong&gt;. I know I’m already repeating myself here, but many seem to overlook this point. The argument for two-state is subject to context.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I keep harping on that because it seems like it gets lost in the conversation. &lt;footnote&gt;1&lt;/footnote&gt;&lt;/p&gt;&lt;p&gt;The pushback against a two-state implementation—despite the aforementioned caveats and nuances—has seemed loud and severe, at least in my circles of the web. Some in the frontend community seem to feel denying users that third option is something akin to malpractice; a choice that nobody who truly cares about users could possibly make. &lt;footnote&gt;2&lt;/footnote&gt;&lt;/p&gt;&lt;p&gt;Perhaps the most visible rebuttal is &lt;a href=&quot;https://www.bram.us/2026/08/18/the-case-for-tri-state-dark-mode-toggles/&quot;&gt;Bramus’s &lt;em&gt;The Case for Tri-State Dark Mode Toggles&lt;/em&gt;&lt;/a&gt;. That post does a serviceable job making its titular case (despite the irony that the site that post is published on offers no theming or theme toggle at all), so it’ll serve as my focus for addressing the dissenting opinion.&lt;/p&gt;&lt;h2&gt;Addressing the case for tri-state theme toggles&lt;/h2&gt;&lt;p&gt;Almost all arguments &lt;em&gt;against&lt;/em&gt; the two-state toggle and in favor of a three-state option boil down to one of two concerns:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Favoring clarity over simplicity; or&lt;/li&gt; &lt;li&gt;Accounting for users who might have a &lt;em&gt;dynamic&lt;/em&gt; system theme, which changes between light and dark mode based on time of day.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The first point is valid, even if I personally believe the tradeoff is worthwhile in most cases (with the caveats already mentioned). It’s possible some users might want that choice, and it’s nice for them to have it if they do. Reasonable cases can be made both ways here, and—once again—context should affect the answer.&lt;/p&gt;&lt;p&gt;Dynamic themes, however, seem like a bit of an extreme edge case to me.&lt;/p&gt;&lt;p&gt;I say “edge case” because, while we don’t have data directly from Apple or other device vendors, what little we &lt;em&gt;do&lt;/em&gt; know from independent surveys suggests a very small minority of users favor a dynamic theme. There’s good reason to think it’s likely a low single-digit percentage of users. &lt;footnote&gt;3&lt;/footnote&gt;&lt;/p&gt;&lt;p&gt;But remember: &lt;strong&gt;the two-state approach isn’t automatically an issue for those users&lt;/strong&gt;. A two-state toggle doesn’t actually become a problem for users with a dynamic OS theme, &lt;em&gt;except&lt;/em&gt; in the following case:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Somebody with a dynamic theme enabled visits the site &lt;em&gt;and&lt;/em&gt; decides to change the site’s theme manually&lt;/li&gt; &lt;li&gt;They come back to the site &lt;em&gt;later&lt;/em&gt;, &lt;em&gt;after&lt;/em&gt; their dynamic theme has shifted between light and dark, &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;Upon that change, the theme is not what the user wants, and they want to change it again&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;That’s a &lt;em&gt;lot&lt;/em&gt; of conditions to build a UI around, and a user has to get through &lt;em&gt;all&lt;/em&gt; of them before we’ve maybe got a problem. Even if we assume 10% of all users have a dynamic OS theme (which, again, is almost certainly an extreme overestimate), &lt;em&gt;some&lt;/em&gt; portion of those users are dropping off at every step above. What we’re left with is quite likely very close to zero.&lt;/p&gt;&lt;p&gt;And &lt;em&gt;even in that case&lt;/em&gt;, let’s remember: the problem in question is literally &lt;em&gt;one extra click&lt;/em&gt;, for whatever vanishingly tiny percentage of users remain at the end of all those logic gates.&lt;/p&gt;&lt;p&gt;Now, to be clear: &lt;strong&gt;I do not advocate for ignoring a group of users simply because they’re in the minority&lt;/strong&gt;. (That’s how a lot of accessibility failures happen, among other terrible outcomes.) That’s not what this is; don’t take this out of context.&lt;/p&gt;&lt;p&gt;Some people have theme preferences for important reasons, like vision. One mode or the other simply doesn’t work for them. We should absolutely respect that.&lt;/p&gt;&lt;p&gt;The difference in this case is: we’re not talking about something like ability, bandwidth, device power, or any other factors a user might not be able to control; we’re talking about &lt;em&gt;a choice they made freely and independently&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Further: we can safely assume this choice is &lt;em&gt;not&lt;/em&gt; accessibility-related, as any user whose vision allows them to opt comfortably into both light and dark mode at varying times of the day clearly isn’t hindered by either one. (More likely, they’re merely concerned with screen brightness, or just variety.)&lt;/p&gt;&lt;p&gt;And in a case where a small minority of users has explicitly &lt;em&gt;opted in&lt;/em&gt; to an unusual experience, and, as a direct result of that uncommon choice, in certain circumstances, &lt;em&gt;might&lt;/em&gt; make further choices that &lt;em&gt;could&lt;/em&gt; cause them to encounter unexpected behavior, depending on what their expectations were to begin with—none of which is actually an &lt;em&gt;issue&lt;/em&gt; for them, but merely a preference, resulting, worst-case, in a single extra click—&lt;/p&gt;&lt;p&gt;Well, I have a very difficult time adding extra complexity for the &lt;em&gt;other&lt;/em&gt; ~95% of users, based solely on that deep hole of what-ifs those users dug for for themselves in the first place.&lt;/p&gt;&lt;p&gt;Personally: I suspect a user who &lt;em&gt;wants&lt;/em&gt; their machine to alter the theme for them probably isn’t picky enough to change it in the first place. But even if they do, I have a hard time imagining that person being terribly surprised or interrupted simply by encountering one or the other, when they’re already used to both.&lt;/p&gt;&lt;p&gt;Or, to put it more bluntly:&lt;/p&gt;&lt;div&gt;&lt;p&gt;I’m not sure real users actually have this problem. I think we’ve mostly invented it.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Which actually brings me to my next point…&lt;/p&gt;&lt;h3&gt;“System” is unclear to many users&lt;/h3&gt;&lt;p&gt;It’s a common pitfall of human psychology that we imagine everyone thinks like we do. Finance people think everyone understands interest and investments; car people think everyone knows how to change their oil; developers think everyone obsesses over color themes.&lt;/p&gt;&lt;p&gt;But I see little evidence of non-computer-nerd users caring nearly as much as we seem to. Many users have a preference, certainly, but I severely doubt most users think even a tiny fraction as much about theming as we do.&lt;/p&gt;&lt;p&gt;Our &lt;a href=&quot;https://en.wikipedia.org/wiki/Curse_of_knowledge&quot;&gt;curse of knowledge&lt;/a&gt; makes us forget not everyone knows everything we do about color themes. Many users likely don’t have any understanding of them at all.&lt;/p&gt;&lt;div&gt;&lt;p&gt;To test my theory, I asked my wife (&lt;em&gt;who is not a developer, but who regularly uses a smartphone, an iPad, and a laptop&lt;/em&gt;) what color theme her phone is set to; light or dark.&lt;/p&gt; &lt;p&gt;She had absolutely no idea.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Despite spending hours a day looking at her phone, she literally had to check it to find out what her default color theme is; it’s that invisible to her. (&lt;em&gt;Interestingly: she herself was surprised to find out she’s actually been looking at dark mode this whole time without even realizing it&lt;/em&gt;).&lt;/p&gt;&lt;p&gt;I asked whether she has the same preference set for her other devices; she didn’t know that, either.&lt;/p&gt;&lt;p&gt;Then I asked whether she’s ever pressed the button on a site or app to change to another color theme.&lt;/p&gt;&lt;p&gt;She said no. Literally never.&lt;/p&gt;&lt;p&gt;Basically: she knew light mode and dark mode both existed, and that was the entirety of her knowledge and experience on the topic, even as it pertained to her own preferences.&lt;/p&gt;&lt;p&gt;Now, &lt;strong&gt;I acknowledge this is only one singular data point&lt;/strong&gt;. I don’t for a moment pretend this is meaningful research, or imagine my wife happens to represent the majority of any user base. Users undoubtedly exist on a wide spectrum between her and, well, nerds who think about this stuff enough to write blog posts about it. (&lt;em&gt;Ahem.&lt;/em&gt;)&lt;/p&gt;&lt;p&gt;But still: this unavoidably indicates that &lt;em&gt;some number of users don’t even know what their system theme is&lt;/em&gt;—or might not even understand what “system” means in the first place!&lt;/p&gt;&lt;p&gt;I suspect a non-trivial number of users probably aren’t even aware they &lt;em&gt;have&lt;/em&gt; a default theme, simply because they’ve gotten so used to whatever is currently set on their device they forgot they ever set anything at all. They think things just look the way they look.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;A “system” setting is actively unhelpful to those users&lt;/strong&gt;. To provide that option is to throw extra complexity at them, which could very well be confusing (&lt;em&gt;especially because, again: two of the three options look exactly alike, and a user might not understand what the difference is, or might even think one of the three is broken&lt;/em&gt;).&lt;/p&gt;&lt;p&gt;And that’s all assuming the word “system” (or “OS” or “device” or whatever) is present to begin with; many three-state toggles simply show a little computer-like icon, which could quite easily compound the confusion.&lt;/p&gt;&lt;p&gt;Bramus at one point asserts a tri-state toggle makes users think less (with a nod to &lt;a href=&quot;https://sensible.com/dont-make-me-think/&quot;&gt;the Steve Krug book&lt;/a&gt;), which, as I hope I’ve made clear by now, is very likely &lt;em&gt;not&lt;/em&gt; the case for a significant number of users. So that appeal to authority is misplaced, in my opinion.&lt;/p&gt;&lt;p&gt;You and I know what that computer icon means, but would everyone you know immediately understand it, without thinking? I suspect not.&lt;/p&gt;&lt;h3&gt;“System” is only useful in highly specific situations&lt;/h3&gt;&lt;p&gt;It’s worth noting that because the “system” option is the default anyway, it’s only important if somebody switches &lt;em&gt;away&lt;/em&gt; from it, &lt;em&gt;and then wants to switch back&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;So in order for the “system” option to be useful, a minimum of &lt;em&gt;four&lt;/em&gt; conditions must first be met:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The user must be aware of what their system theme is;&lt;/li&gt; &lt;li&gt;The user must &lt;em&gt;also&lt;/em&gt; understand what that little computer icon represents (or otherwise, the option must clearly be labeled);&lt;/li&gt; &lt;li&gt;The user must have &lt;em&gt;already&lt;/em&gt; switched away from their system theme, since it’s the default, &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;The user must want to switch &lt;em&gt;back&lt;/em&gt; to their system theme—and explicitly &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; just to whatever the opposite of the current theme happens to be&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I suspect few users regularly, if ever, check all of those boxes.&lt;/p&gt;&lt;p&gt;I further suspect most who do are either already highly knowledgeable in this area, or are just deliberately running every setting through its paces for the sake of testing. (Personally, testing scenarios are the only times I can ever remember clicking the “system” option.)&lt;/p&gt;&lt;p&gt;Regardless, because people seem to skip over this point, I think it’s worth reiterating once more: &lt;strong&gt;this doesn’t mean we should never use a three-state toggle&lt;/strong&gt;. They have their place, and context is important. I (and Dr. Verou, and others who favor two-state toggles) are completely in favor of three-state where it makes sense.&lt;/p&gt;&lt;p&gt;I just don’t think that’s in the main site UI.&lt;/p&gt;&lt;div&gt;&lt;p&gt;The push to make tri-state the default is mostly in the name of accommodating an extreme minority of users that &lt;em&gt;probably doesn’t even need accommodation in the first place&lt;/em&gt;, at the expense of likely confusing or slowing down at least as many more.&lt;/p&gt;&lt;/div&gt;&lt;h3&gt;Questionable data&lt;/h3&gt;&lt;p&gt;In &lt;em&gt;The Case for Tri-State Dark Mode Toggles&lt;/em&gt;, linked above, Bramus points to a survey he conducted on social media, asking his followers whether they preferred a two- or three-state toggle. The survey closed with the overwhelming preference being three-state, but there are several issues with this approach to data collection:&lt;/p&gt;&lt;p&gt;First: &lt;strong&gt;people will always &lt;em&gt;say&lt;/em&gt; they want more options&lt;/strong&gt;. Psychologically, we imagine ourselves to prefer choice, even when we don’t actually use it or even &lt;em&gt;want&lt;/em&gt; it in practice. The higher number pretty much always wins in a survey like this, regardless of topic. (Ask me how many options I’d like to have for dipping sauce, and I’ll probably say five or ten. But ask me how many I actually &lt;em&gt;use&lt;/em&gt;, and I’ll probably say just one or two.) &lt;footnote&gt;4&lt;/footnote&gt;&lt;/p&gt;&lt;p&gt;Secondly: Bramus hints there’s an option he prefers, which could further skew the results; respondents might be compelled to guess what Bramus thinks the right answer is, rather than supply their own.&lt;/p&gt;&lt;p&gt;But most importantly: &lt;strong&gt;this audience is likely to be heavily comprised of developers&lt;/strong&gt;. The result is therefore bound to reflect the opinions of developers, rather than the preferences of users. It means we’re probably hearing almost entirely from those with uncommonly deep knowledge of the topic (and uncommonly strong opinions on it), rather than from a representative group of users, whose own understanding and preferences could very well be different.&lt;/p&gt;&lt;h3&gt;The Vale approach&lt;/h3&gt;&lt;p&gt;In his post, Bramus also points to &lt;a href=&quot;https://vale.rocks/micros/20260810-0330&quot;&gt;a tri-state implementation by Vale&lt;/a&gt; (AKA Declan Chidlow) that he feels may be the ideal middle ground, but I can’t say I agree it’s worthy of consideration. Here’s an embedded CodePen demo:&lt;/p&gt;&lt;p&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/editor/OuterVale/pen/019fe98c-2d9b-71e4-849d-726543589635&quot;&gt;Light/Dark/System Theme Setting&lt;/a&gt; by Declan Chidlow (&lt;a href=&quot;https://codepen.io/OuterVale&quot;&gt;@OuterVale&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;It’s certainly clever; it &lt;em&gt;appears&lt;/em&gt; to be a two-state toggle, but defaults to system preference by having &lt;em&gt;neither&lt;/em&gt; option selected. If you &lt;em&gt;do&lt;/em&gt; pick either theme, you can click it again to remove it, which causes the toggle to once more fall back to the system default.&lt;/p&gt;&lt;p&gt;I get why that feels nice. If somebody wanted to use this on their personal website or a small app, I’d say go for it. It’s neat.&lt;/p&gt;&lt;p&gt;But I would personally advocate &lt;em&gt;against&lt;/em&gt; this approach at any meaningful scale.&lt;/p&gt;&lt;p&gt;My main issue with this implementation is: &lt;strong&gt;there is no existing affordance like this&lt;/strong&gt;. There’s no common user control anywhere I’m aware of that matches this toggle or works like it; it’s novel, invented behavior, which users are unlikely to have ever encountered before. That’s bound to be unavoidably confusing, regardless of how clever it might be or what feature it’s actually manipulating under the hood.&lt;/p&gt;&lt;p&gt;It’s effectively a three-state toggle masquerading as a two-state, with properties of both checkboxes and radio buttons intermingled. Neither its true nature nor the option to deselect the current theme is at all apparent or discoverable, except by trial. (It’s ironic, then, that this approach is favored by some who critique the Verou approach over its lack of clarity and transparency.)&lt;/p&gt;&lt;p&gt;I suppose you could argue the Vale toggle functions as a two-way toggle frictionlessly enough, with a nice secret third option some users might learn about and use. But if you’re actually concerned about the system option being easily accessible, it makes for a strange choice.&lt;/p&gt;&lt;p&gt;[Edited 8/22/2026] &lt;a href=&quot;https://front-end.social/@sarajw/117135267012668951&quot;&gt;Sara Joy pointed out on Mastodon&lt;/a&gt; that one advantage of Vale’s approach is: it makes what state you’re switching &lt;em&gt;to&lt;/em&gt; abundantly clear. I agree that’s one very nice thing about it, and about tri-state, for that matter. This also brings up the question of what &lt;em&gt;should&lt;/em&gt; be shown on a two-state toggle; the &lt;em&gt;current&lt;/em&gt; state, or the &lt;em&gt;new&lt;/em&gt; state the toggle will switch to if pressed, which I don’t think I have a strong opinion on at this point, but which I suspect could spark an entirely new lively debate all on its own.&lt;/p&gt;&lt;h2&gt;So what’s the right approach?&lt;/h2&gt;&lt;p&gt;After all of this, we we circle back once again to the original post, because despite all the pushback, Dr. Verou got it right in the first place. As always: &lt;em&gt;it depends&lt;/em&gt;.™&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;If a toggle is a part of the main UI, make it a two-state toggle&lt;/strong&gt;. This serves users who simply want to change the theme from whatever it happens to be at the moment, which will be the overwhelming majority of users, and the main purpose of any control that’s featured in the main user interface. Don’t give somebody configuration when they’re just looking for the light switch.&lt;/p&gt;&lt;/li&gt; &lt;li&gt;&lt;p&gt;&lt;strong&gt;If it’s in a settings or account page, an explicit three-state toggle is probably better&lt;/strong&gt;. If a user is in the mindset of setting up an app how they prefer, or choosing for their account, the choice is warranted. In those cases, users are deciding what experience they want to have &lt;em&gt;in the future&lt;/em&gt;, and not just at this specific moment. (And, we have the space to dedicate to labeling each option clearly.)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Further, I’d say any app or site a user might be looking at all day long, or multiple times a day—Slack or GitHub, for example—probably warrants a three-way toggle. Most sites and apps won’t fall into this category, but some will.&lt;/p&gt;&lt;p&gt;But here I’m mostly just repeating what’s already been written quite clearly by Dr. Lea Verou.&lt;/p&gt;&lt;p&gt;Who is a woman.&lt;/p&gt;&lt;p&gt;With a PhD.&lt;/p&gt;&lt;p&gt;In usability.&lt;/p&gt;&lt;p&gt;From MIT.&lt;/p&gt;&lt;p&gt;So, ya know…maybe she knows what she’s talking about, dudes of the internet.&lt;/p&gt;&lt;footer&gt;&lt;ol&gt;&lt;li&gt;Honestly, it feels like a &lt;em&gt;lot&lt;/em&gt; of the pushback to Dr. Verou’s post is rooted in a hasty or incomplete reading; it seems many don’t get past the title, and take the post as a dogmatic screed against three-way toggles. It very much is not.&lt;/li&gt;&lt;li&gt;&lt;em&gt;Won’t somebody please think of the users!?&lt;/em&gt;&lt;/li&gt;&lt;li&gt;Most of the data we have to go off of doesn’t explicitly include dynamic theme usage; it only includes users who say they use both light and dark themes. So while that number seems to be around 10%, that’s probably very high, as it likely includes users who manually change their theme or bounce between light and dark, alongside those who opt for automatic dynamic theming. Also: not all operating systems even offer the setting; Windows does not, and while Android does, it’s a bit of work to enable unless you opt into it during device setup.&lt;/li&gt;&lt;li&gt;Honestly, I think the psychological fear of missing out is a big driver of the pushback against two-state toggles as well. People are convinced that they—or users—will miss that option if it’s not present, not unlike how I’m convinced someday I’ll miss that doohickey that’s been sitting unused in my cabinet for 15 years.&lt;/li&gt;&lt;/ol&gt;&lt;/footer&gt;</content:encoded>
</item>
<item>
<title>Knowledge Catalog: KI-Agenten sicher nutzen</title>
<link>https://www.berger.team/kuenstliche-intelligenz/knowledge-catalog-fuer-ki-agenten-so-machst-du-unternehmenswissen-sicher-nutzbar/</link>
<enclosure type="image/jpeg" length="0" url="https://www.berger.team/wp-content/uploads/2025/11/20566-das-beste-aus-zwei-welten-spezialistenwissen-ohne-agentur-overhead.jpg"></enclosure>
<guid isPermaLink="false">DxypeK140KuE-E_Ny-4aqjqsnEBbpFH3zyqTAQ==</guid>
<pubDate>Thu, 27 Aug 2026 00:41:05 +0000</pubDate>
<description>Ein Knowledge Catalog gibt KI-Agenten verlässlichen Geschäftskontext: Metadaten, Zugriffsregeln, Datenqualität und klare Verantwortung...</description>
<content:encoded>&lt;p&gt;Ein Knowledge Catalog macht Unternehmenswissen für KI-Agenten auffindbar, verständlich, überprüfbar und kontrolliert nutzbar. Genau darum geht es: Nicht jede KI soll alles lesen dürfen, sondern die richtigen Informationen im richtigen Geschäftskontext nutzen — mit klaren Zugriffsregeln, Verantwortlichkeit, Datenqualität und menschlicher Kontrolle.&lt;/p&gt;&lt;p&gt;Ich sehe bei KMU immer wieder dasselbe Muster: Die Begeisterung für KI ist groß, aber das Wissen darunter ist verstreut. Angebote liegen in alten Ordnern, Produktdetails in Word-Dokumenten, Website-Texte in WordPress, Kundendaten im CRM, Projektwissen in E-Mails, Entscheidungen in Chatverläufen und Kennzahlen in Excel oder BigQuery. Für Menschen ist dieses Chaos mühsam. Für KI-Agenten ist dieses Chaos ein Risiko.&lt;/p&gt;&lt;p&gt;Ein &lt;a href=&quot;https://www.berger.team/glossar/knowledge-catalog/&quot;&gt;Knowledge Catalog&lt;/a&gt; ist deshalb kein reines Enterprise-Thema. Für kleine und mittlere Unternehmen ist ein Knowledge Catalog ein pragmatisches Ordnungssystem: Er erklärt, welche Informationen existieren, was diese Informationen bedeuten, welche Quelle vertrauenswürdig ist, wer zuständig ist und was ein KI-System damit tun darf. Das ist praktisches Wissensmanagement für Unternehmen, die KI nicht nur testen, sondern verantwortbar einsetzen wollen.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;KI ist kein Shortcut. KI verstärkt das, was im Unternehmen bereits klar, gepflegt und verantwortet ist — oder das Chaos, das schon vorhanden ist.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Wenn Du KI-Agenten sinnvoll einsetzen willst, brauchst Du nicht zuerst das größte Tool. Du brauchst zuerst Klarheit über Dein eigenes Wissen. Genau hier beginnt KI-Readiness: nicht bei der Software, sondern bei der Frage, ob Dein Unternehmen weiß, was es weiß.&lt;/p&gt;&lt;h2&gt;Warum ein Knowledge Catalog für KI-Agenten wichtig wird&lt;/h2&gt;&lt;p&gt;Ein &lt;a href=&quot;https://www.berger.team/glossar/ki-agent-ai/&quot;&gt;KI-Agent&lt;/a&gt; ist mehr als ein Chatfenster. Ein KI-Agent kann Ziele verfolgen, Aufgaben planen, Werkzeuge nutzen, Informationen abrufen und einzelne Prozessschritte selbstständig vorbereiten oder ausführen. Je mehr ein KI-Agent darf, desto wichtiger wird der Datenkontext.&lt;/p&gt;&lt;p&gt;Roher Datenzugriff beantwortet nur eine technische Frage: „Wo liegen die Daten?“ Ein Knowledge Catalog beantwortet die unternehmerisch wichtigeren Fragen:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Was bedeutet diese Information?&lt;/strong&gt; „Umsatz“ kann brutto, netto, bezahlt, gebucht, prognostiziert oder storniert bedeuten.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Welche Quelle ist verbindlich?&lt;/strong&gt; Die Website, das CRM, die Buchhaltung oder die letzte Angebotsvorlage?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wie aktuell ist die Information?&lt;/strong&gt; Ein veralteter Preisbaustein kann ein Angebot falsch machen, auch wenn die KI technisch sauber arbeitet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wer ist verantwortlich?&lt;/strong&gt; Ohne klare Zuständigkeit korrigiert niemand falsche Definitionen, doppelte Dateien oder widersprüchliche Aussagen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Welche Nutzung ist erlaubt?&lt;/strong&gt; Eine interne Notiz darf vielleicht analysiert, aber nicht in eine externe Kundenmail übernommen werden.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Welche Daten sind sensibel?&lt;/strong&gt; Kundendaten, Mitarbeiterdaten, Margen, Beschwerden und Verträge brauchen besondere Schutzregeln.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Ohne diese Klärung entstehen nicht nur technische Fehler. Es entstehen falsche Angebote, uneinheitliche Kommunikation, schlechte Reports, Datenschutzrisiken und automatisierte Entscheidungen, die niemand sauber verantwortet.&lt;/p&gt;&lt;h2&gt;Das eigentliche Risiko: KI beschleunigt falsches Wissen&lt;/h2&gt;&lt;p&gt;Viele Unternehmen fragen zuerst: „Wie können wir KI schneller nutzen?“ Ich frage meistens zurück: „Welche falschen, alten oder widersprüchlichen Informationen würde die KI dann schneller verwenden?“&lt;/p&gt;&lt;p&gt;Ein paar typische Beispiele aus KMU-Projekten:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Falsche Umsatzdefinition:&lt;/strong&gt; Das Vertriebsteam spricht von Angebotswert, die Buchhaltung von bezahlten Rechnungen und die Geschäftsführung von geplantem Jahresumsatz. Ein KI-Agent erzeugt daraus ein Reporting, das plausibel klingt, aber drei Logiken vermischt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Veraltete Angebotsbausteine:&lt;/strong&gt; In einem Ordner liegen fünf Versionen derselben Leistungsbeschreibung. Die KI findet die alte Version mit einem nicht mehr gültigen Preis und baut daraus ein neues Angebot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uneinheitliches Markenwissen:&lt;/strong&gt; Die Website spricht anders als die Verkaufsunterlagen, Social Media nutzt andere Begriffe und interne Präsentationen enthalten alte Positionierungen. Die KI schreibt dann formal passend, aber nicht markenklar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sensible Kundendaten:&lt;/strong&gt; Ein Agent fasst Projektinformationen zusammen und übernimmt interne Bemerkungen, die niemals in eine Kundenkommunikation gehören.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support ohne Freigabeprozess:&lt;/strong&gt; Ein KI-System beantwortet Anfragen mit alten Garantiebestimmungen, weil niemand den gültigen Stand als Single Source of Truth definiert hat.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Das Problem ist nicht, dass KI grundsätzlich ungeeignet ist. Das Problem ist, dass ein KI-Agent ohne Geschäftskontext nicht wissen kann, welche Information verbindlich, aktuell, erlaubt oder riskant ist.&lt;/p&gt;&lt;h2&gt;Data Catalog, Knowledge Catalog und Owned Knowledge einfach erklärt&lt;/h2&gt;&lt;p&gt;Ein &lt;strong&gt;Data Catalog&lt;/strong&gt; beschreibt vor allem Datenbestände: Tabellen, Spalten, Dateien, Systeme, technische Metadaten und Herkunft. Das ist wichtig, aber für KI-Agenten oft zu wenig.&lt;/p&gt;&lt;p&gt;Ein &lt;strong&gt;Knowledge Catalog&lt;/strong&gt; geht weiter. Ein Knowledge Catalog verbindet Datenquellen mit Bedeutung, Geschäftskontext, Verantwortlichkeit, Datenqualität, Freigabeprozess und erlaubter Nutzung. Aus einer technischen Datenliste wird eine verständliche Wissenskarte für Menschen und KI-Systeme.&lt;/p&gt;&lt;p&gt;Der Unterschied lässt sich praktisch so beschreiben:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Catalog:&lt;/strong&gt; „Diese Tabelle existiert, diese Spalten sind vorhanden, diese Datei liegt dort.“&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowledge Catalog:&lt;/strong&gt; „Diese Quelle ist verbindlich, diese Kennzahl bedeutet genau das, diese Person ist verantwortlich, diese Daten dürfen für interne Analyse verwendet werden, aber nicht für externe Kommunikation.“&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Für KMU kommt ein weiterer strategischer Begriff dazu: &lt;strong&gt;Owned Knowledge&lt;/strong&gt;. Owned Knowledge bedeutet, dass Dein wichtiges Unternehmenswissen nicht nur in Plattformen, Chatverläufen, Ordnern oder Köpfen einzelner Menschen liegt. Dein Wissen ist strukturiert, exportierbar, pflegbar und für Menschen sowie Maschinen nutzbar.&lt;/p&gt;&lt;p&gt;Viele Betriebe haben verstanden, warum &lt;a href=&quot;https://www.berger.team/allgemein/owned-media-vs-rented-media-der-strategische-wert-einer-eigenen-domain/&quot;&gt;Owned Media wichtiger ist als gemietete Reichweite&lt;/a&gt;. Die gleiche Logik gilt jetzt für Wissen: Wenn Dein Unternehmen sein Wissen nicht selbst strukturiert, werden Plattformen, Tools und einzelne Personen zur Abhängigkeit.&lt;/p&gt;&lt;h2&gt;Was gehört in einen Knowledge Catalog?&lt;/h2&gt;&lt;p&gt;Ein Knowledge Catalog muss nicht alles enthalten. Ein guter Knowledge Catalog enthält zuerst das Wissen, das für Entscheidungen, Kommunikation und wiederkehrende Arbeit wichtig ist.&lt;/p&gt;&lt;p&gt;Für ein KMU reichen zum Start meist diese Bausteine:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quellenverzeichnis:&lt;/strong&gt; Website, CRM, Angebotsvorlagen, Produktdaten, Projektmanagement, Buchhaltung, Cloud-Ordner, interne Dokumentation, Support-Wissen und Analyse-Tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metadaten:&lt;/strong&gt; Name der Quelle, Beschreibung, System, Format, Aktualität, Eigentümer, Sensibilität und Freigabestatus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Begriffsdefinitionen:&lt;/strong&gt; klare Erklärungen für Lead, Kunde, Auftrag, Umsatz, Marge, Projektstatus, Conversion, Wartung, Garantie oder Stammkunde.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single Source of Truth:&lt;/strong&gt; eindeutige Festlegung, welche Quelle bei Widersprüchen gilt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verantwortliche:&lt;/strong&gt; Menschen, die fachlich entscheiden können, ob eine Information korrekt ist.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zugriffsregeln:&lt;/strong&gt; wer lesen, ändern, exportieren, analysieren oder KI-Zugriff erlauben darf.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datenqualität:&lt;/strong&gt; Hinweise zu Aktualität, Vollständigkeit, Dubletten, Fehlern, Unsicherheit und Freigabe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beziehungen:&lt;/strong&gt; Verknüpfungen zwischen Produkten, Zielgruppen, Angeboten, Prozessen, Kampagnen, Kennzahlen und Verantwortlichen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testfragen:&lt;/strong&gt; typische Fragen, die ein Mensch oder KI-Agent korrekt beantworten können muss.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Erlaubte KI-Nutzung:&lt;/strong&gt; klare Regeln, ob ein Agent Informationen nur lesen, zusammenfassen, analysieren, vorschlagen, ausführen oder extern kommunizieren darf.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Gerade der letzte Punkt ist entscheidend. Ein KI-Agent, der interne Dokumente zusammenfasst, ist ein anderes Risiko als ein KI-Agent, der automatisch Angebote erstellt, Kundendaten verändert oder E-Mails verschickt.&lt;/p&gt;&lt;h2&gt;Was darf ein KI-Agent mit welcher Quelle tun?&lt;/h2&gt;&lt;p&gt;Ich empfehle kleinen Teams eine einfache Rechte-Logik. Nicht jede Quelle braucht denselben Schutz, aber jede wichtige Quelle braucht eine Entscheidung.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lesen:&lt;/strong&gt; Der KI-Agent darf Informationen abrufen, aber nichts verändern. Geeignet für freigegebene Wissensartikel, öffentliche Website-Inhalte oder interne Prozessbeschreibungen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zusammenfassen:&lt;/strong&gt; Der KI-Agent darf Inhalte verdichten, etwa Projektstände oder Meetingnotizen. Sensible Aussagen müssen geprüft werden.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analysieren:&lt;/strong&gt; Der KI-Agent darf Muster erkennen, Kennzahlen vergleichen oder Fehlerhinweise geben. Voraussetzung sind definierte Begriffe und Datenqualität.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vorschlagen:&lt;/strong&gt; Der KI-Agent darf Angebotsbausteine, Antworten oder nächste Schritte vorbereiten. Ein Mensch gibt das Ergebnis frei.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ausführen:&lt;/strong&gt; Der KI-Agent darf Aktionen anstoßen, etwa Tickets erstellen oder Datensätze aktualisieren. Das braucht enge Regeln, Protokollierung und klare Verantwortlichkeit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extern kommunizieren:&lt;/strong&gt; Der KI-Agent darf Inhalte nach außen senden. Das sollte in KMU nur mit sehr klaren Grenzen und menschlicher Kontrolle passieren.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Diese Abstufung wirkt einfach, verhindert aber viele Probleme. Denn sie zwingt Dich, zwischen „KI darf mir helfen“ und „KI darf für mich handeln“ zu unterscheiden.&lt;/p&gt;&lt;h2&gt;Beispiele: Wo ein Knowledge Catalog im KMU-Alltag konkret hilft&lt;/h2&gt;&lt;h3&gt;Angebotsvorbereitung&lt;/h3&gt;&lt;p&gt;Bei Angeboten wird Wissenschaos sofort sichtbar. Welche Leistung ist aktuell? Welche Formulierung ist freigegeben? Welche Annahmen gelten? Welche Referenzen passen? Welche Rabatte sind erlaubt?&lt;/p&gt;&lt;p&gt;Ein Knowledge Catalog kann Leistungsbeschreibungen, Preislogiken, Angebotsbausteine, Referenzen, Zielgruppen und interne Regeln so ordnen, dass ein KI-System sinnvolle Vorarbeit leistet. Der Agent erstellt dann nicht irgendein Angebot, sondern arbeitet mit freigegebenem Wissen. Wenn Du genau hier Zeit verlierst, ist der Beitrag zur &lt;a href=&quot;https://www.berger.team/allgemein/ki-gestuetzte-angebotserstellung-wie-suedtiroler-dienstleister-5-stunden-pro-woche-sparen/&quot;&gt;KI-gestützten Angebotserstellung&lt;/a&gt; eine gute Vertiefung.&lt;/p&gt;&lt;h3&gt;Interne Suche und Onboarding&lt;/h3&gt;&lt;p&gt;Neue Teammitglieder fragen oft dieselben Dinge: Wo liegt was? Wer entscheidet was? Wie läuft ein Projektübergang? Welche Formulierungen verwenden wir? Welche Tools sind verbindlich?&lt;/p&gt;&lt;p&gt;Mit einem Knowledge Catalog entsteht eine semantische Suche. Semantische Suche bedeutet: Das System sucht nicht nur nach exakten Wörtern, sondern nach Bedeutung. Wenn jemand „Wie übergeben wir ein Website-Projekt?“ fragt, sollte das System Projektablauf, Checkliste, Verantwortliche, Vorlagen und Freigaberegeln finden.&lt;/p&gt;&lt;h3&gt;Website- und Markenwissen&lt;/h3&gt;&lt;p&gt;Als Branding-Stratege ist mir dieser Bereich besonders wichtig. Eine Marke besteht nicht nur aus Logo und Farben. Eine Marke besteht aus Bedeutung, Sprache, Haltung, Zielgruppenverständnis und wiederkehrenden Entscheidungen.&lt;/p&gt;&lt;p&gt;Wenn Dein Markenwissen sauber strukturiert ist, kann KI konsistenter schreiben, Inhalte prüfen und Widersprüche erkennen. Das betrifft Tonalität, Leistungsversprechen, verbotene Aussagen, Belege, Zielgruppen, Fachbegriffe und Positionierung. In unserer Arbeit rund um &lt;a href=&quot;https://www.berger.team/leistungen/branding/&quot;&gt;Branding und Positionierung&lt;/a&gt; wird deshalb immer klar: KI kann Markenarbeit unterstützen, aber sie darf Haltung nicht ersetzen.&lt;/p&gt;&lt;h3&gt;Reporting und Kennzahlen&lt;/h3&gt;&lt;p&gt;Reporting wirkt oft objektiv, ist aber in vielen KMU voller Interpretationen. Was ist ein aktiver Kunde? Wann zählt ein Angebot als gewonnen? Welche Kosten gehören in die Marge? Welche Kampagne bekommt die Conversion zugerechnet?&lt;/p&gt;&lt;p&gt;Ein Knowledge Catalog definiert diese Begriffe. Dadurch kann ein KI-Agent Reports besser erklären, Abweichungen einordnen und Fragen natürlicher beantworten. Google beschreibt zum Beispiel für Gemini in BigQuery Funktionen wie natürliche Sprache zur SQL-Generierung und Data Canvas zur Exploration von Datenassets. Gemini in Looker erlaubt Fragen an Datenquellen in natürlicher Sprache. Auch hier gilt: Natürliche Sprache macht schlechte Definitionen nicht besser.&lt;/p&gt;&lt;h3&gt;Support und Kundenkommunikation&lt;/h3&gt;&lt;p&gt;Ein KI-Agent im Support kann wiederkehrende Fragen schneller beantworten. Aber nur, wenn Garantiebedingungen, Leistungsgrenzen, aktuelle Produktinformationen und Eskalationsregeln gepflegt sind.&lt;/p&gt;&lt;p&gt;Ohne Knowledge Catalog kann Support-Automation Vertrauen beschädigen. Mit Knowledge Catalog kann ein Agent erkennen: Diese Antwort ist freigegeben, diese Information ist veraltet, diese Anfrage enthält sensible Daten, dieser Fall muss an einen Menschen.&lt;/p&gt;&lt;h3&gt;Projektübergaben&lt;/h3&gt;&lt;p&gt;In kleinen Teams steckt Projektwissen oft in den Köpfen einzelner Menschen. Wenn jemand krank ist, wechselt oder überlastet ist, entstehen Lücken. Ein Knowledge Catalog hilft, Übergaben sauberer zu machen: Projektziel, Status, offene Entscheidungen, Risiken, Ansprechpartner, Dateien, nächste Schritte und Freigaben werden auffindbar.&lt;/p&gt;&lt;h2&gt;MCP einfach erklärt: kontrollierte Brücke statt Tool-Hype&lt;/h2&gt;&lt;p&gt;Das &lt;strong&gt;Model Context Protocol&lt;/strong&gt;, kurz &lt;strong&gt;MCP&lt;/strong&gt;, ist ein standardisierter Weg, um KI-Anwendungen mit externen Datenquellen und Werkzeugen zu verbinden. Anthropic kündigte MCP am 25. November 2024 als offenen Standard für sichere Zwei-Wege-Verbindungen zwischen Datenquellen und KI-gestützten Tools an.&lt;/p&gt;&lt;p&gt;Für Dich als Unternehmer ist nicht die technische Architektur entscheidend. Wichtig ist die Logik: MCP kann eine kontrollierte Brücke zwischen KI-Agenten, Werkzeugen und freigegebenem Wissen sein. Ein Agent greift dann nicht unkontrolliert auf alles zu, sondern nutzt definierte Schnittstellen mit klaren Regeln.&lt;/p&gt;&lt;p&gt;Google Cloud dokumentiert, dass der frühere Dataplex Universal Catalog seit dem 10. April 2026 &lt;strong&gt;Knowledge Catalog&lt;/strong&gt; heißt und Geschäftskontext, Governance sowie einen Kontextgraphen für KI-Agenten bereitstellen soll. Google beschreibt außerdem Knowledge-Catalog-Anbindungen mit MCP, Gemini CLI und MCP Toolbox. Für KMU ist daran vor allem die Denklogik relevant: MCP und Knowledge Catalog können zusammen helfen, KI-Agenten kontrolliert mit freigegebenem Unternehmenswissen zu verbinden.&lt;/p&gt;&lt;p&gt;Das ist ein aktuelles Produktbeispiel, aber nicht die einzige mögliche Umsetzung. Ein KMU kann mit Microsoft-Systemen, WordPress, CRM, lokalen Dateien, Notion, einem Wiki oder einer eigenen Datenstruktur starten. Die zentrale Frage bleibt immer gleich: Welches Wissen ist freigegeben, verständlich, aktuell und verantwortet?&lt;/p&gt;&lt;h2&gt;Data Governance ist kein Bürokratiemonster&lt;/h2&gt;&lt;p&gt;Viele kleine Unternehmen reagieren skeptisch auf &lt;strong&gt;Data Governance&lt;/strong&gt;. Verständlich. Niemand braucht zusätzliche Bürokratie. Aber gute Data Governance bedeutet nicht mehr Papierarbeit. Gute Data Governance bedeutet weniger Risiko, weniger Suchzeit und weniger falsche Entscheidungen.&lt;/p&gt;&lt;p&gt;Für KMU heißt Data Governance vor allem:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;klare Zuständigkeiten&lt;/strong&gt; statt „das weiß wahrscheinlich jemand“,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;freigegebene Quellen&lt;/strong&gt; statt Dateien mit Namen wie „final_final_neu“,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;verständliche Begriffe&lt;/strong&gt; statt interner Abkürzungen, die nur zwei Menschen verstehen,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;geprüfte Zugriffe&lt;/strong&gt; statt blindem Vertrauen in jedes neue Tool,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;regelmäßige Pflege&lt;/strong&gt; statt einmaliger Aufräumaktion,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;menschliche Kontrolle&lt;/strong&gt; dort, wo eine Antwort rechtlich, finanziell oder menschlich relevant wird.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Besonders bei personenbezogenen Daten ist das nicht optional. Nach Art. 5 Abs. 1 DSGVO müssen personenbezogene Daten zweckgebunden, auf das notwendige Maß beschränkt und durch geeignete technische und organisatorische Maßnahmen geschützt werden. Für KI-Agenten heißt das: Nicht alles, was technisch lesbar ist, darf auch verarbeitet werden.&lt;/p&gt;&lt;h2&gt;Welche Daten gehören nicht ungeprüft in KI-Systeme?&lt;/h2&gt;&lt;p&gt;Ein Knowledge Catalog soll nicht alles öffnen. Ein Knowledge Catalog soll helfen, bewusst zu entscheiden. Besonders vorsichtig solltest Du bei diesen Daten sein:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;personenbezogene Kundendaten:&lt;/strong&gt; Namen, E-Mail-Adressen, Telefonnummern, Adressen, Kaufhistorien, Beschwerden oder Gesundheitsinformationen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mitarbeiterdaten:&lt;/strong&gt; Verträge, Gehälter, Leistungsbeurteilungen, Krankheitsdaten oder interne Konflikte.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finanzdaten:&lt;/strong&gt; Margen, Liquidität, interne Kalkulationen, nicht veröffentlichte Umsätze oder Bankdaten.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vertragsinformationen:&lt;/strong&gt; Sonderkonditionen, rechtliche Verpflichtungen, Vertraulichkeitsklauseln oder laufende Verhandlungen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strategische Informationen:&lt;/strong&gt; Preisstrategie, neue Produkte, interne Schwächen, Akquisepläne oder Wettbewerbsanalysen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ungeprüfte Notizen:&lt;/strong&gt; Rohprotokolle, private Kommentare, nicht freigegebene Einschätzungen oder emotionale Zwischenstände.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Diese Daten können in bestimmten Fällen nutzbar sein. Aber nicht ohne Zweck, Zugriffskonzept, technische Schutzmaßnahmen, Protokollierung und klare Freigabe.&lt;/p&gt;&lt;h2&gt;Die Minimalversion für kleine Teams&lt;/h2&gt;&lt;p&gt;Du musst nicht mit einer großen Plattform starten. Für viele kleine Teams reicht eine einfache Minimalversion, die konsequent gepflegt wird.&lt;/p&gt;&lt;p&gt;Eine schlanke Startversion kann so aussehen:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ein zentrales Dokument&lt;/strong&gt; mit den wichtigsten Quellen, Verantwortlichen und Regeln,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;eine Wiki- oder Notion-Struktur&lt;/strong&gt; für Begriffe, Prozesse und Freigaben,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ein definierter Ordner&lt;/strong&gt; mit freigegebenen Angebotsbausteinen und aktuellen Leistungsbeschreibungen,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ein CMS-Bereich&lt;/strong&gt; für Markenwissen, Leistungsseiten, Zielgruppen und Belege,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;eine einfache Quellenliste&lt;/strong&gt; mit Status: freigegeben, veraltet, unsicher, sensibel, nicht für KI-Nutzung.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Das klingt unspektakulär. Genau deshalb funktioniert es. Kleine Unternehmen brauchen am Anfang kein schweres System, sondern einen verbindlichen Ort und klare Pflege.&lt;/p&gt;&lt;h2&gt;30- bis 90-Tage-Plan: So startest Du pragmatisch&lt;/h2&gt;&lt;p&gt;Ein Knowledge Catalog wird nicht durch ein Tool eingeführt, sondern durch Entscheidungen. Ich würde den Einstieg in drei Phasen aufteilen.&lt;/p&gt;&lt;h3&gt;Phase 1: Die ersten 30 Tage — Anwendungsfall und Quellen klären&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wähle einen konkreten Use Case:&lt;/strong&gt; Angebotsvorbereitung, interne Suche, Reporting, Support oder Onboarding.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formuliere das Ziel:&lt;/strong&gt; Welche Arbeit soll leichter, sicherer oder schneller werden?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Liste die wichtigsten Quellen:&lt;/strong&gt; maximal 10 bis 20 Quellen für den ersten Durchlauf.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markiere sensible Quellen:&lt;/strong&gt; Kundendaten, Mitarbeiterdaten, Finanzdaten und vertrauliche Dokumente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bestimme die Single Source of Truth:&lt;/strong&gt; Welche Quelle gilt, wenn Informationen widersprüchlich sind?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sammle echte Fragen:&lt;/strong&gt; Welche Fragen stellt Dein Team heute immer wieder?&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Nach 30 Tagen musst Du nicht fertig sein. Du solltest wissen, welcher Bereich zuerst geordnet wird und welche Quellen dafür zählen.&lt;/p&gt;&lt;h3&gt;Phase 2: Tage 31 bis 60 — Begriffe, Qualität und Verantwortung definieren&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Definiere zentrale Begriffe:&lt;/strong&gt; Schreibe auf, was Umsatz, Lead, Projekt, Kunde, Auftrag, Marge oder Status bedeuten.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lege Verantwortliche fest:&lt;/strong&gt; Jede wichtige Quelle braucht eine fachliche Ansprechperson.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bewerte Datenqualität:&lt;/strong&gt; aktuell, veraltet, unvollständig, doppelt, widersprüchlich oder geprüft.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Definiere Zugriffsregeln:&lt;/strong&gt; Wer darf lesen, ändern, exportieren oder KI-Zugriff erhalten?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Baue einen einfachen Freigabeprozess:&lt;/strong&gt; Wer prüft neue Inhalte, bevor ein KI-Agent sie nutzen darf?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entferne oder sperre Altlasten:&lt;/strong&gt; Alte Preislisten und ungültige Vorlagen sind riskanter als fehlende Inhalte.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Nach 60 Tagen sollte Dein erster Knowledge-Catalog-Bereich so klar sein, dass ein neuer Mensch im Team die Logik versteht.&lt;/p&gt;&lt;h3&gt;Phase 3: Tage 61 bis 90 — KI-Zugriff begrenzen und testen&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Erstelle Testfragen:&lt;/strong&gt; Nutze echte Fragen aus Angeboten, Support, Reporting oder Onboarding.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Teste Antworten gegen Expertenwissen:&lt;/strong&gt; Stimmen Quellen, Definitionen und Schlussfolgerungen?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Begrenze KI-Rechte:&lt;/strong&gt; Am Anfang lesen, zusammenfassen und vorschlagen — nicht automatisch ausführen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protokolliere Fehler:&lt;/strong&gt; Jede falsche Antwort zeigt Dir, welche Quelle, Definition oder Regel fehlt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Führe einen Pflege-Rhythmus ein:&lt;/strong&gt; monatlich für operative Inhalte, quartalsweise für Begriffe und Regeln.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entscheide den nächsten Use Case:&lt;/strong&gt; Erst wenn ein Bereich stabil ist, kommt der nächste.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Wenn Du vorher Deinen Reifegrad einschätzen willst, kann ein &lt;a href=&quot;https://www.berger.team/kuenstliche-intelligenz/ki-readiness-check-fuer-kmu-reifegrad-und-90-tage-plan/&quot;&gt;KI-Readiness-Check für KMU&lt;/a&gt; helfen. KI-Readiness bedeutet nicht, dass alles perfekt sein muss. KI-Readiness bedeutet, dass Risiken, Daten, Prozesse und Verantwortlichkeiten bewusst geklärt werden.&lt;/p&gt;&lt;h2&gt;Checkliste: Ist Dein Unternehmen bereit für den ersten Knowledge Catalog?&lt;/h2&gt;&lt;p&gt;Wenn Du bei mehreren Punkten innerlich nickst, ist der richtige Zeitpunkt gekommen:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Dein Team sucht regelmäßig nach denselben Informationen.&lt;/li&gt;
&lt;li&gt;Es gibt mehrere Versionen von Angebotsvorlagen, Preislisten oder Leistungsbeschreibungen.&lt;/li&gt;
&lt;li&gt;Wichtige Prozessinformationen stecken in einzelnen Köpfen.&lt;/li&gt;
&lt;li&gt;Kennzahlen werden unterschiedlich interpretiert.&lt;/li&gt;
&lt;li&gt;CRM, Website, Buchhaltung, Projektmanagement und Cloud-Ordner enthalten jeweils einen Teil der Wahrheit.&lt;/li&gt;
&lt;li&gt;Du willst KI-Agenten für Angebote, Support, Reporting oder interne Suche nutzen.&lt;/li&gt;
&lt;li&gt;Es gibt sensible Kundendaten oder vertrauliche Projektdaten.&lt;/li&gt;
&lt;li&gt;Niemand kann spontan sagen, welche Quelle verbindlich ist.&lt;/li&gt;
&lt;li&gt;Neue Teammitglieder brauchen lange, um interne Abläufe zu verstehen.&lt;/li&gt;
&lt;li&gt;Du möchtest KI nutzen, ohne Verantwortung an ein Tool auszulagern.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Wann ist ein Knowledge Catalog gut genug für den ersten KI-Agenten?&lt;/h2&gt;&lt;p&gt;Ein Knowledge Catalog ist gut genug für den ersten KI-Agenten, wenn fünf Bedingungen erfüllt sind:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Der Use Case ist klar:&lt;/strong&gt; Der Agent hat eine begrenzte Aufgabe, keine Allzweckrolle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Die Quellen sind freigegeben:&lt;/strong&gt; Der Agent nutzt nur geprüfte oder bewusst markierte Informationen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Die Begriffe sind definiert:&lt;/strong&gt; Zentrale Kennzahlen und Fachwörter sind eindeutig beschrieben.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Die Rechte sind begrenzt:&lt;/strong&gt; Der Agent darf am Anfang eher vorbereiten als ausführen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Der Mensch bleibt im Freigabeprozess:&lt;/strong&gt; Kritische Ergebnisse werden geprüft, bevor sie Wirkung nach außen oder in Systeme hinein entfalten.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Das ist der Punkt, an dem KI praktisch wird. Nicht perfekt. Aber kontrolliert, lernfähig und verantwortbar.&lt;/p&gt;&lt;h2&gt;Warum das auch eine Markenfrage ist&lt;/h2&gt;&lt;p&gt;Ein Knowledge Catalog klingt zuerst nach Datenmanagement. In Wahrheit ist er auch Markenarbeit. Denn jede automatische Antwort, jedes Angebot, jeder Support-Text und jeder Report prägt, wie Dein Unternehmen handelt und wahrgenommen wird.&lt;/p&gt;&lt;p&gt;Wenn Dein Unternehmenswissen unklar ist, kommuniziert auch Deine KI unklar. Wenn Deine Marke sauber positioniert ist, Deine Website verständlich strukturiert ist und Deine Prozesse nachvollziehbar sind, kann KI diese Klarheit verstärken.&lt;/p&gt;&lt;p&gt;Deshalb denken wir bei Berger+Team Branding, Website, Marketing, Automation und KI nicht getrennt. In unserer Arbeit rund um &lt;a href=&quot;https://www.berger.team/ki-loesungen/&quot;&gt;KI und Digitalisierung&lt;/a&gt; geht es nicht darum, ein Tool einzubauen. Es geht darum, ein System zu schaffen, das Deinem Team Arbeit abnimmt und bessere Entscheidungen ermöglicht.&lt;/p&gt;&lt;h2&gt;Mein Fazit: KI braucht Klarheit, nicht nur Zugriff&lt;/h2&gt;&lt;p&gt;Ein Knowledge Catalog ist die Brücke zwischen Unternehmenswissen und verlässlichen KI-Agenten. Der eigentliche Wert liegt nicht im Katalog selbst, sondern in der Klarheit, die dadurch entsteht: Welche Daten sind relevant? Was bedeuten diese Daten im Geschäftskontext? Wer ist verantwortlich? Was darf ein Agent tun? Was muss ein Mensch freigeben?&lt;/p&gt;&lt;p&gt;Für KMU ist das eine echte Chance. Kleine Unternehmen können schneller entscheiden, pragmatischer strukturieren und näher am eigenen Wissen arbeiten als große Organisationen. Aber nur, wenn KI nicht als Abkürzung missverstanden wird.&lt;/p&gt;&lt;p&gt;Mein Rat: Baue zuerst Dein Owned Knowledge auf. Starte klein, aber verbindlich. Wähle einen Use Case, kläre Quellen, definiere Begriffe, lege Verantwortliche fest, begrenze KI-Zugriffe und prüfe Ergebnisse menschlich. Dann wird KI nicht zum unkontrollierten Experiment, sondern zu einem Werkzeug, das Klarheit verstärkt.&lt;/p&gt;&lt;h2&gt;FAQ: Knowledge Catalog, KI-Agenten und Unternehmenswissen&lt;/h2&gt;&lt;h3&gt;Was ist ein Knowledge Catalog?&lt;/h3&gt;&lt;p&gt;Ein Knowledge Catalog ist eine strukturierte Wissens- und Metadatenkarte Deines Unternehmens. Er beschreibt, welche Daten, Inhalte und Prozesse existieren, was sie bedeuten, wer verantwortlich ist, welche Qualität sie haben und wie Menschen oder KI-Agenten sie nutzen dürfen.&lt;/p&gt;&lt;h3&gt;Was ist der Unterschied zwischen einem Knowledge Catalog und einem Data Catalog?&lt;/h3&gt;&lt;p&gt;Ein Data Catalog zeigt vor allem technische Datenbestände wie Tabellen, Dateien, Spalten und Systeme. Ein Knowledge Catalog ergänzt diese Sicht um Geschäftskontext, Begriffsdefinitionen, Verantwortlichkeit, Datenqualität, Freigabeprozess und erlaubte KI-Nutzung.&lt;/p&gt;&lt;h3&gt;Warum brauchen KI-Agenten Geschäftskontext?&lt;/h3&gt;&lt;p&gt;KI-Agenten brauchen Geschäftskontext, damit sie Informationen richtig einordnen können. Ohne Kontext kann ein Agent alte Preise, falsche Umsatzdefinitionen oder sensible Notizen verwenden und daraus scheinbar plausible, aber riskante Ergebnisse erzeugen.&lt;/p&gt;&lt;h3&gt;Wie klein kann ein KMU starten?&lt;/h3&gt;&lt;p&gt;Ein KMU kann sehr klein starten: mit einem Use Case, einer Quellenliste, einem Glossar für zentrale Begriffe und klaren Verantwortlichen. Für den Anfang reicht oft ein Wiki, ein strukturiertes Dokument oder ein freigegebener Ordner, wenn Pflege und Zugriffsregeln verbindlich sind.&lt;/p&gt;&lt;h3&gt;Was kostet der Einstieg in einen Knowledge Catalog?&lt;/h3&gt;&lt;p&gt;Der Einstieg hängt weniger vom Tool als vom Umfang ab. Wenn Du mit einem klaren Anwendungsfall startest, entstehen die größten Aufwände meist durch Sichtung, Bereinigung, Definitionen und Freigaben — nicht durch Software.&lt;/p&gt;&lt;h3&gt;Welche Tools brauche ich für einen Knowledge Catalog?&lt;/h3&gt;&lt;p&gt;Für den Start brauchst Du nicht zwingend ein spezialisiertes Enterprise-Tool. Ein Wiki, Notion, SharePoint, ein sauberes Ordnersystem, ein CMS-Bereich oder ein internes Dokument können reichen; wichtig sind Struktur, Verantwortlichkeit, Aktualität und klare Regeln für KI-Zugriff.&lt;/p&gt;&lt;h3&gt;Wer sollte im Unternehmen verantwortlich sein?&lt;/h3&gt;&lt;p&gt;Die Verantwortung sollte nicht allein bei der IT liegen. Fachverantwortliche müssen entscheiden, ob Inhalte korrekt sind, während Geschäftsführung oder Projektleitung Regeln für Zugriff, Freigabe und Nutzung festlegen.&lt;/p&gt;&lt;h3&gt;Welche Daten dürfen nicht ungeprüft in KI-Systeme?&lt;/h3&gt;&lt;p&gt;Personenbezogene Daten, Mitarbeiterdaten, Finanzdaten, Vertragsinformationen, interne Strategien und ungeprüfte Notizen dürfen nicht ungeprüft in KI-Systeme. Diese Daten brauchen Zweckbindung, Datenminimierung, Zugriffsschutz und eine klare Entscheidung, ob ein KI-Agent sie überhaupt verarbeiten darf.&lt;/p&gt;&lt;h3&gt;Was hat MCP mit einem Knowledge Catalog zu tun?&lt;/h3&gt;&lt;p&gt;MCP steht für Model Context Protocol und kann KI-Anwendungen kontrolliert mit freigegebenen Datenquellen und Werkzeugen verbinden. In Verbindung mit einem Knowledge Catalog hilft MCP, dass ein KI-Agent nicht beliebig auf Daten zugreift, sondern definierte Quellen, Regeln und Grenzen nutzt.&lt;/p&gt;&lt;h3&gt;Wie hilft ein Knowledge Catalog gegen KI-Halluzinationen?&lt;/h3&gt;&lt;p&gt;Ein Knowledge Catalog reduziert Halluzinationen, weil KI-Agenten auf geprüfte Quellen, definierte Begriffe und freigegebenes Wissen zugreifen. Der Katalog ersetzt keine menschliche Kontrolle, aber er gibt dem KI-System eine belastbare Grundlage.&lt;/p&gt;&lt;h3&gt;Wann darf ein KI-Agent automatisch handeln?&lt;/h3&gt;&lt;p&gt;Ein KI-Agent sollte erst automatisch handeln, wenn Quellen, Regeln, Verantwortliche, Tests und Protokollierung stabil sind. In KMU ist es meist sinnvoll, zuerst mit Lesen, Zusammenfassen und Vorschlagen zu starten und kritische Aktionen durch Menschen freigeben zu lassen.&lt;/p&gt;&lt;h3&gt;Wie oft muss ein Knowledge Catalog gepflegt werden?&lt;/h3&gt;&lt;p&gt;Operative Inhalte wie Preise, Leistungen und Support-Regeln solltest Du mindestens monatlich prüfen. Begriffe, Zugriffsregeln und Datenqualität kannst Du in vielen KMU quartalsweise überprüfen, solange es bei wichtigen Änderungen einen direkten Freigabeprozess gibt.&lt;/p&gt;&lt;h2&gt;Quellen&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anthropic.com/news/model-context-protocol&quot;&gt;Anthropic: Introducing the Model Context Protocol — anthropic.com (2024)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cloud.google.com/dataplex/docs/introduction&quot;&gt;Google Cloud Documentation: Knowledge Catalog overview — docs.cloud.google.com (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cloud.google.com/dataplex/docs/pre-built-tools-with-mcp-toolbox&quot;&gt;Google Cloud Documentation: Use Knowledge Catalog with MCP, Gemini, and other agents — docs.cloud.google.com (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cloud.google.com/bigquery/docs/gemini-overview&quot;&gt;Google Cloud Documentation: Gemini in BigQuery overview — docs.cloud.google.com (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/looker/docs/overview-gemini&quot;&gt;Google Cloud Documentation: Gemini in Looker overview — cloud.google.com (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eur-lex.europa.eu/eli/reg/2016/679/oj&quot;&gt;Verordnung (EU) 2016/679, Datenschutz-Grundverordnung, Art. 5 — eur-lex.europa.eu (2016)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded>
</item>
<item>
<title>OpenAPI für Unternehmensservices: vom Prozess zur Agent API</title>
<link>https://www.berger.team/website/openapi-fuer-unternehmensservices-vom-prozess-zur-agent-api/</link>
<guid isPermaLink="false">0K6KpyhRBzh_Z8AwZejjs0QyHcictqf2L8O1lw==</guid>
<pubDate>Thu, 27 Aug 2026 00:40:57 +0000</pubDate>
<description>OpenAPI macht Unternehmensservices maschinenlesbar und als API-Vertrag überprüfbar. Der Leitfaden zeigt KMU, wie sie Prozesse, Daten, Berechtigungen und menschliche Freigaben kontrolliert in eine Service-Schnittstelle übersetzen.</description>
<content:encoded>&lt;p&gt;OpenAPI macht Unternehmensservices maschinenlesbar und als API-Vertrag überprüfbar. Der Leitfaden zeigt KMU, wie sie Prozesse, Daten, Berechtigungen und menschliche Freigaben kontrolliert in eine Service-Schnittstelle übersetzen.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://www.berger.team/wp-content/uploads/2026/04/22457-9e3bfea5-die-10-files-die-deine-website-ki-ready-machen.jpg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>我讨厌红点</title>
<link>https://blog.3qin.us/red_dot_is_evil.html</link>
<enclosure type="image/jpeg" length="0" url="https://mail.3qin.us/~derek/blog_imgs/pics/derek.jpg"></enclosure>
<guid isPermaLink="false">NmHOsvhyERHl9f9jp2NGMISDpimHRUTK618BlQ==</guid>
<pubDate>Wed, 26 Aug 2026 21:27:04 +0000</pubDate>
<description>不知道从什么时候开始，各种应用的用户界面都出现了红点这个东西。有红点的地方，通常是内容有更新，鼓励用户点进去。点进去再出来，红点自动消失。短信如此，微信如此，这种用户界面元素已经普及到生活中方方面面的应用，我想大家都已经熟悉并不假思索地去点有红点的地方了吧。我不否认它合理有效，但今天我偏要挑一挑它的毛病。</description>
<content:encoded>&lt;p&gt;不知道从什么时候开始，各种应用的用户界面都出现了红点这个东西。有红点的地方，通常是内容有更新，鼓励用户点进去。点进去再出来，红点自动消失。短信如此，微信如此，这种用户界面元素已经普及到生活中方方面面的应用，我想大家都已经熟悉并不假思索地去点有红点的地方了吧。我不否认它合理有效，但今天我偏要挑一挑它的毛病。&lt;/p&gt;&lt;h2&gt;从用户角度&lt;/h2&gt;&lt;p&gt;首先，它很丑。红点点在那里，我想大多数人都觉得不是很爽吧。我没有什么洁癖，但我可以观察到有点洁癖的人，常常会努力地去把所有的红点点掉，从而还给应用界面一个清净。当点红点已经不是实现目的的手段而是目的本身的时候，它的意义也就不大了，反而它的负面作用就越来越显著：让用户焦虑，降低用户的注意力时长，把用户训练成为一个点红点的机器。&lt;/p&gt;&lt;p&gt;再者，红点这种用户界面元素有被滥用的趋势：应用有更新来个红点，有小功能想向用户推荐也是红点，再往后是不是有广告也会是红点呢？这个问题其实是上个问题的延申，当用户已经被训练成不假思索地去点红点的时候，应用作者和用户的角力就进一步失衡，应用作者在资本的影响下更可以为所欲为，挤压用户的注意力，让用户成为应用的奴隶。红点虽然不是罪魁祸首，但它确实是一个奴化圈养用户的重要手段。&lt;/p&gt;&lt;p&gt;最后，我想说的是点或不点什么，看或不看什么，是用户的自由，也是用户的隐私。诚然，你点了什么，看了什么，应用有本事知道，并能搜集数据到后台丰富你的用户绘像，有没有红点它们都做的到。但是，红点给了它们一个光明正大的理由来搜集这种数据：我们本不想记录用户什么看过什么没看过，是为了实现红点这种用户需要的功能勉为其难嘛。当一个卑劣的目的披上了合理的外衣，用户也就见怪不怪了。&lt;/p&gt;&lt;h2&gt;从应用开发者角度&lt;/h2&gt;&lt;p&gt;我不仅是个用户，我自己也做一点&lt;a href=&quot;https://roastidio.us&quot;&gt;互联网应用&lt;/a&gt;。从应用开发者角度，这个功能实现也是给我不少困扰的。&lt;/p&gt;&lt;p&gt;已阅状态应该是什么时候标记的呢？我能想到的有下面三种可能：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;点的这个动作触发&lt;/li&gt;
&lt;li&gt;第一次呈现给用户时触发&lt;/li&gt;
&lt;li&gt;当用户从呈现页面点到别处的时候触发&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;深究下以上三种方法都有问题。在第一种方法下，用户点了可能未必看，而且内容入口可能有多个，每个入口都要做动作吗？在第二种方法下，用户仍然可能看0.1秒或者1分钟，应用端无法区分，甚至无法区分是人还是机器。最后一种方法貌似最精确，可惜用户想离开的话可能直接关窗口，应用来不及动作。所以，已阅与否永远是不精确的，应用的状态通常和用户的期望有相当差距。&lt;/p&gt;&lt;p&gt;另一个问题就在于我完全不想对用户做绘像，所以，实现已阅与否的状态存储和及时切转对我来说没有好处，只有负担，而这个负担还不小：我可能有很多内容，我也可能有很多用户，这是个 MxN 的问题，如此大的状态空间，存储和维护对我都有不小的压力。&lt;/p&gt;&lt;h2&gt;没有红点行不行&lt;/h2&gt;&lt;p&gt;没有红点，应用作者还是有不少方法和用户沟通：需要用户立即反应的时候可以弹窗，这种需求应该很罕见，所以动用弹窗这种强硬手段不会引起用户不满。需要用户延时反应的时候可以推送消息，给用户时间自主反应。这种需求比第一种需求多些，也不是天天都有。以上两点都是红点时代之前就常用的方法，现在也没有过时。&lt;/p&gt;&lt;p&gt;红点有一定意义的地方就在于告诉用户哪些内容有更新，这样防止用户点进去发现没更新导致失望，或者忽略了更新导致错过了。没有红点，应用还是可以对内容根据更新时间排序，有心思的用户可以从头一个一个点，直到自己觉得无聊为止。失望或错过是上网不可避免的遭遇，我不觉得我有必要给我的用户营造一个假象。&lt;/p&gt;&lt;p&gt;说到底，红点是当一个页面内容太多太繁杂，为了给用户提供一个视觉上的优先级提示而应运而生的。我觉得问题的核心不是该如何提醒用户什么可以先点，什么可以不着急点，而是该如何精简内容，解放用户。这样不仅解决了用户界面问题，还可以让用户早点放下手机，合上电脑，这才是双赢。&lt;/p&gt;&lt;p&gt;可惜，双赢通常不是商业应用的目的。&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Nobody has reached the bottom (yet) · Ata Kuyumcu&#39;s Blog</title>
<link>https://blog.lvmbdv.dev/posts/nobody-has-reached-the-bottom/</link>
<enclosure type="image/jpeg" length="0" url="https://blog.lvmbdv.dev/images/dungeon-farm-cover.png"></enclosure>
<guid isPermaLink="false">qtl654-z6INuVgHiupMlDnIs7_4BcesPDxNogQ==</guid>
<pubDate>Wed, 26 Aug 2026 20:33:33 +0000</pubDate>
<description>Three days of analytics from the dungeon under this blog: 1,040 entries, 530 sessions that met a stranger, 683 deaths, and a floor at depth 13 that nobody has seen at the time of writing.</description>
<content:encoded>&lt;div&gt;&lt;strong&gt;Update, 2026-08-13&lt;/strong&gt;&lt;br/&gt;The title lasted eight hours. Four sessions have reached depth 13, and all four
walked back out through the arch with their gold: 178, 252, 273 and 635. The
rest of the post is left as it was.&lt;/div&gt;&lt;p&gt;The dungeon under this blog has a bottom now. Depth 13, with a sensible end to
the gameplay loop. The rest of this post is three-ish days of analytics from
&lt;a href=&quot;https://blog.lvmbdv.dev/posts/theres-a-dungeon-under-this-blog/&quot;&gt;the dungeon&lt;/a&gt;, and the caveats are at
the end because they matter more than usual.&lt;/p&gt;&lt;h2&gt;The door&lt;/h2&gt;&lt;p&gt;1,040 entries against 4,216 visits in the same window. A quarter of the people
who came to the blog got in, which sounds better than it is, because most of
them came for a post that lists the ways in.&lt;/p&gt;&lt;p&gt;The ranking is what I didn’t predict:&lt;/p&gt;&lt;pre&gt;pie showData
    title Ways in
    &amp;quot;tapped&amp;quot; : 575
    &amp;quot;konami&amp;quot; : 251
    &amp;quot;dungeon&amp;quot; : 95
    &amp;quot;xyzzy&amp;quot; : 75
    &amp;quot;iddqd&amp;quot; : 26
    &amp;quot;delve&amp;quot; : 16&lt;/pre&gt;&lt;p&gt;Tapping the footer was the afterthought. It exists because the Konami code needs
a keyboard and a lot of readers might not have one, and it took about as long to
write as this paragraph. It beat every other path, surprisingly.&lt;/p&gt;&lt;h2&gt;Most players did meet somebody&lt;/h2&gt;&lt;p&gt;The first post ended by saying I had modest expectations about two strangers
finding each other down there. 530 sessions saw another living player. There
were 1,040 entries, and 893 sessions the close event managed to catch.&lt;/p&gt;&lt;p&gt;I want to be careful about why that happened, because it isn’t the netcode.
Everyone joins a room named after the UTC date, so the only thing standing
between two readers is whether they were reading at the same time. For three
days they usually were. The blog served 4,216 visits in that window against
15,171 in its entire history, which is to say the dungeon has spent its whole
life inside one spike.&lt;/p&gt;&lt;p&gt;When the spike flattens, the meeting rate goes to about zero, and no amount of
WebRTC changes that. A multiplayer easter egg on a personal blog is a party that
works while people are still arriving. I knew that in the abstract before I
built it. The number makes it specific: the feature I’m fondest of has a
half-life of a few days.&lt;/p&gt;&lt;p&gt;One thing in here I didn’t expect at all. Graves started holding the dead
player’s purse yesterday, so walking over somebody’s death pays. Three graves
have been robbed. Two of them belonged to a stranger.&lt;/p&gt;&lt;h2&gt;Where people die&lt;/h2&gt;&lt;p&gt;Depth 2 kills more people than depth 1 in raw count, on 43% fewer arrivals. That
isn’t tuning. It’s the monster table climbing faster than ten hit points can
absorb, and depth 2 is where a player who’s still pressing arrow keys politely
finds out.&lt;/p&gt;&lt;p&gt;The bigger drop is earlier, and it isn’t deaths. 996 people arrived at depth 1.
144 died there. 568 went deeper. The other 284 stood in a stone room, took no
damage worth dying to, and left. The first floor is where people decide whether
they’re playing.&lt;/p&gt;&lt;p&gt;The whole funnel, floor by floor:&lt;/p&gt;&lt;pre&gt;---
config:
  sankey:
    nodeAlignment: left
    labelStyle: outlined
    height: 420
---
sankey-beta

entered,d1,996
entered,stayed in the village,44
d1,d2,568
d2,d3,389
d3,d4,262
d4,d5,164
d5,d6,115
d6,d7,59
d7,d8,48
d8,d9,24
d9,d10,14
d10,d11,3&lt;/pre&gt;&lt;p&gt;Every step is the arrival count from the table, so the gap between one floor and
the next is everyone who died there or stopped. Depth 11 is the one step that
isn’t measured: the arrival event recorded nobody down there, and the three
deaths are people a fall trap dropped out of depth 10, by a route that never
fires an arrival. Falls feed the floors above it too, which is why the descent
counts run low and every per-depth rate in this post is a ceiling.&lt;/p&gt;&lt;p&gt;Traps sprang 1,110 times, 872 gas and 238 falls. 258 chests turned out to have
teeth. The dark came for someone 82 times, all of them on depth 5 or deeper,
which is the only place it lives.&lt;/p&gt;&lt;h2&gt;What’s down there that nobody has used&lt;/h2&gt;&lt;p&gt;Zero sessions have reached depth 13. Zero have walked back out through the arch,
which is the only exit that isn’t dying and the only way gold survives a run.
The deepest anyone has been is 11, twice.&lt;/p&gt;&lt;p&gt;The rest of the tail is about as thin. 437 of 893 sessions killed nothing at
all; one killed 114. 702 ended holding no gold; two ended holding 224. The
median session is 31 seconds.&lt;/p&gt;&lt;p&gt;That last number is the honest shape of an easter egg. Most people open the
door, look at the room, and close it.&lt;/p&gt;&lt;h2&gt;So I built a farm&lt;/h2&gt;&lt;p&gt;319 of 893 sessions never went down the stairs. 36% of the people who find a
secret dungeon do not want a dungeon. They wanted to find something, and they
did.&lt;/p&gt;&lt;p&gt;The village used to be a well, a signpost and an arch. Yesterday it got a farm:
a farmer with three lines about the hole under the arch, and a cow, a sheep and
chickens that wander a tile at a time and say something when you walk into them.
The talk event fires once per speaker per session, so it counts sessions that
found the cow rather than how many times somebody walked into a cow.&lt;/p&gt;&lt;p&gt;Eight sessions found the farmer, five the chickens, four the cow, four the
sheep. Twenty-one bumps, one day up, on the wrong side of the spike. That’s a
baseline, not a verdict.&lt;/p&gt;&lt;h2&gt;What the numbers can’t say&lt;/h2&gt;&lt;p&gt;More of this than I’d like.&lt;/p&gt;&lt;p&gt;Falls didn’t count as descents until this morning. A fall trap drops you a floor
by a route that never fired the arrival event, 238 times, which is why the table
above has three deaths at depth 11 and nobody arriving there, and why depths 5,
7 and 9 read as deadlier than they are. Every per-depth rate in this post is a
ceiling.&lt;/p&gt;&lt;p&gt;The session event rode &lt;code&gt;pagehide&lt;/code&gt; until yesterday. Mobile browsers kill hidden
tabs without firing it, which is where the missing sessions went. It fires on
first background now, and that trades one bias for another: a reader who tabs
away and comes back is measured up to the moment they left, so long sessions
read short.&lt;/p&gt;&lt;p&gt;The trap event carries the kind and the depth as two properties of the same
event, and I can’t join two properties through a share link. I know there were
872 gas traps. I have no idea where any of them sprang.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://umami.is/&quot;&gt;Umami&lt;/a&gt; derives its session id from IP and user agent and
resets it daily, so “did anyone come back the next day” isn’t a question I can
ask.&lt;/p&gt;&lt;p&gt;Some of it is coarse because I made it coarse. The session event carries four
numbers about the run and nothing about the person: no input method, no step
count, no path through the floor.&lt;/p&gt;&lt;p&gt;I’ve stopped adding rooms. Depth 13 is the end and the few floors above it stay
deliberately empty so the final approach is a walk rather than a fight, and at
some point somebody is going to get there.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>WebSockets vs. SSE should be about ordering and correctness - Dashbit Blog</title>
<link>https://dashbit.co/blog/websockets-vs-sse</link>
<guid isPermaLink="false">Yftyf1KguBO5cNOqjiD04Fs2cx7drstj6NdfYw==</guid>
<pubDate>Wed, 26 Aug 2026 16:45:14 +0000</pubDate>
<description>The discussion around delivering HTML over WebSockets vs. Server-Sent Events (SSE) with Fetch should really be about event ordering and correctness. Let&#39;s understand why.</description>
<content:encoded>&lt;h1&gt;WebSockets vs. SSE should be about ordering and correctness&lt;/h1&gt;&lt;p&gt;
A couple days ago, Andros, the maintainer of Django LiveView, wrote an article called &lt;a href=&quot;https://en.andros.dev/blog/ef4968f5/html-over-websockets-real-time-spas-with-barely-any-javascript/&quot;&gt;HTML over WebSockets: real-time SPAs with barely any JavaScript&lt;/a&gt;. While the article does an overall good job of outlining the pros and cons of WebSockets and the LiveView architecture (reduced payloads, simplified stack, etc.), the top comment in the Hacker News discussion for said article says:&lt;/p&gt;&lt;blockquote&gt;
  &lt;p&gt;
For most apps just use SSE and the built-in code for making HTTP requests (Fetch) instead of hacking up your own client side JS to make requests over a WebSocket. The latency is the same because modern browsers multiplex HTTP requests over a single TCP connection that is left open.  &lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;
To begin with, while Fetch will indeed reuse existing connections, requests are still stateless and therefore every request has to do the same work of decrypting session information, fetching the user from database or cache, and so on. With WebSockets, the connection is authenticated once and the user data is in memory, avoiding all this extra work. Furthermore, in the case of Phoenix LiveView, it actually &lt;a href=&quot;https://dashbit.co/blog/latency-rendering-liveview&quot;&gt;sends diffs over the wire, rather than HTML&lt;/a&gt;, which reduces payload sizes drastically.&lt;/p&gt;&lt;p&gt;
However, I’d say those benefits are secondary. The discussion around delivering HTML over WebSockets vs. Server-Sent Events (SSE) with Fetch should really be about event ordering and correctness. When using different data streams, it is very easy to receive and render events in the wrong order, resulting in confusing user experiences or misleading users into making the wrong decisions. As we will see, attempts to fix this typically mean more client complexity, increased latency, or both!&lt;/p&gt;&lt;h2&gt;
The ordering issue&lt;/h2&gt;&lt;p&gt;
Imagine you have an article with three tags: “erlang”, “clojure”, and “javascript”. Then, at the same time, you decide to add the “elixir” tag, while another user decides to remove the “javascript” tag. Here is what most people expect to happen:&lt;/p&gt;&lt;div&gt;
  Loading event sequence…&lt;/div&gt;&lt;p&gt;
In the example above, the database ends up with a list of “erlang”, “clojure”, and “elixir”. And your UI shows the exact same list, with “erlang”, “clojure”, and “elixir”. All is good!&lt;/p&gt;&lt;p&gt;
However, thanks to the network, garbage-collectors, proxies, and other factors, the following is also a possible ordering of those events:&lt;/p&gt;&lt;div&gt;
  Loading event sequence…&lt;/div&gt;&lt;p&gt;
In this new version, the delete operation executed first, but its update with the list of tags arrived later. This means your interface will briefly flash a list with “erlang”, “clojure”, and “elixir” as tags, but ultimately display only “erlang” and “clojure”. Basically, your interface will act as if the “elixir” tag was not added at all. And it won’t fix itself unless you refresh or a new event is sent over the wire.&lt;/p&gt;&lt;p&gt;
At this point, some people like to yell, “that’s fine, it is eventually consistent.” Except that’s not what eventually consistent means. Generally speaking, an eventually consistent system guarantees that, if no new updates happen, all copies of the data will eventually converge to the same value. That won’t happen here. The interface can remain stale indefinitely, until you refresh the page or another event happens to bring it back in sync.&lt;/p&gt;&lt;p&gt;
The root cause of the issue above is data being delivered over two different streams, it is not really intrinsic to SSE. For example, any application using WebSockets + Fetch to deliver different streams that update the same UI components would have the same race. The difference is that WebSockets are bidirectional, so you can do both reads and writes over the same connection:&lt;/p&gt;&lt;div&gt;
  Loading event sequence…&lt;/div&gt;&lt;p&gt;
In the example above, the WebSocket connection is responsible for processing user events and recomputing updates, ensuring the correct tags list is delivered regardless of the order of events.&lt;/p&gt;&lt;h2&gt;
Making concurrent requests work&lt;/h2&gt;&lt;p&gt;
As we have seen above, when using SSE + Fetch, we may have two concurrent requests. If both deliver data, we can have data races as there is no causal ordering between them.&lt;/p&gt;&lt;p&gt;
The simplest solution is to make it so only one of those streams deliver updates. For example, you could make it so Fetch only performs the update but doesn’t deliver or render the tags list. Instead, the client waits until the update is delivered via SSE. The trouble with this approach is increased latency, as you need to rely on a queue system to deliver your own updates between servers:&lt;/p&gt;&lt;div&gt;
  Loading latency sequence…&lt;/div&gt;&lt;p&gt;
The above is actually similar to how the long polling transport works in Phoenix. Except that we can skip one hop in Phoenix, as the nodes can communicate directly with each other via Distributed Erlang:&lt;/p&gt;&lt;div&gt;
  Loading latency sequence…&lt;/div&gt;&lt;p&gt;
Using a bidirectional connection can skip those additionals hops altogether:&lt;/p&gt;&lt;div&gt;
  Loading latency sequence…&lt;/div&gt;&lt;p&gt;
Because WebSockets are bidirectional, they provide a cheap way to preserve ordering between client operations and their responses, without the need to route your own operations through a separate delivery channel.&lt;/p&gt;&lt;p&gt;
Another alternative is to use the SSE channel to simply tell the client, “hey, refresh, you have new data.” Then you don’t need additional hops on the server for your own writes. However, it will most likely lead to increased server load, as you must do additional requests to fetch the latest data instead of them being delivered over SSE. Also, you must &lt;a href=&quot;https://dashbit.co/blog/remix-concurrent-submissions-flawed&quot;&gt;be careful not to perform multiple fetches concurrently&lt;/a&gt;, instead queue operations on the client and give higher priority to user interactions.&lt;/p&gt;&lt;p&gt;
You could even make multiple streams work by receiving the events out of order and ordering them on the client. This unfortunately sounds much simpler than it actually is. For example, you may have no guarantee that an event creating a resource arrives before its deletion, or that the order of updates matches the database operations. That’s why there are whole platforms, &lt;a href=&quot;https://electric.ax&quot;&gt;like Electric&lt;/a&gt;, designed to solve those issues for you.&lt;/p&gt;&lt;h2&gt;
Summing up&lt;/h2&gt;&lt;p&gt;
Next time you are debating WebSockets versus SSE, ask yourself: how will I guarantee events are delivered in the right order, and avoid confusing users with stale or incorrect data?&lt;/p&gt;&lt;p&gt;
Whenever you have multiple streams of data, they can race each other, regardless if you are using WebSockets or SSE. WebSockets give you a bidirectional channel that can act as a simple foundation for preserving causal ordering between user actions and server updates. SSE + Fetch can be made to work, but it often comes with additional latency from routing data internally. And regardless of whether you pick WebSockets or SSE, Phoenix gives you the best bang for your buck, as you can simplify your operations and reduce latency thanks to Elixir and Distributed Erlang.&lt;/p&gt;&lt;blockquote&gt;
  &lt;p&gt;
This article was fully written by a human and proof-read by LLMs. The interactive examples were fully produced by a coding agent under adult supervision.  &lt;/p&gt;
&lt;/blockquote&gt;</content:encoded>
</item>
<item>
<title>Migrating from Codeberg Pages to an OpenBSD VPS - Nemin&#39;s Blog</title>
<link>https://nemin.hu/vps/index.html</link>
<enclosure type="image/jpeg" length="0" url="https://nemin.hu/logo.png"></enclosure>
<guid isPermaLink="false">M88x5O9kqnvnb6_9tKRaCdIwm1PUKZxKCJtUVg==</guid>
<pubDate>Tue, 25 Aug 2026 17:05:50 +0000</pubDate>
<description>Moving my content over to Netcup.</description>
<content:encoded>&lt;div&gt;&lt;h2&gt;Table of Contents&lt;/h2&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt; Background&lt;ul&gt;&lt;li&gt; Picking my provider&lt;/li&gt;&lt;li&gt; SSH config&lt;ul&gt;&lt;li&gt; Be careful with MIMEs&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt; Upgrading OpenBSD to
8.0-beta&lt;/li&gt;&lt;li&gt; Firewall&lt;/li&gt;&lt;li&gt; TLS certificates&lt;/li&gt;&lt;li&gt; Web server&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt; German bureaucracy&lt;/li&gt;&lt;li&gt; Setting up my VPS&lt;/li&gt;&lt;li&gt; Findings; or, Bots Love Your
Obscure Content!&lt;/li&gt;&lt;li&gt; Conclusion&lt;/li&gt;&lt;li&gt; Footnotes&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;In this post I&amp;#39;d like to explain the &amp;quot;how&amp;quot;-s and &amp;quot;why&amp;quot;-s of moving
from forge-based static site hosting to a full on VPS.&lt;/p&gt;&lt;p&gt;This isn&amp;#39;t really a tutorial, mostly because there are far better
texts out there how one would do these things, but I do link to many of
them and point out stuff I found confusing or surprising.&lt;/p&gt;&lt;p&gt;Also, if you&amp;#39;re reading this post, you&amp;#39;re reading it served by the
VPS, say hi!&lt;/p&gt;&lt;h1&gt;&lt;span&gt;1&lt;/span&gt; Background&lt;/h1&gt;&lt;p&gt;I&amp;#39;ve been blogging for almost eight years now and for those eight
years, I&amp;#39;ve always relied on some sort of free host for my content. This
seemed fine for a pretty long time, especially considering neither this
nor my &lt;a href=&quot;https://oddwords.hu&quot;&gt;other site&lt;/a&gt; is monetized at all.
However, recent-ish changes in my needs and financial situation finally
got me to break with the status quo and actually experiment a
little.&lt;/p&gt;&lt;p&gt;Originally, I relied on Github Pages, which was for a site like mine
almost perfect. It was free and required nearly zero setup, it built
almost immediately, it had impeccable uptime (something Github famously
&lt;a href=&quot;https://mrshu.github.io/github-statuses/&quot;&gt;started to struggle
with&lt;/a&gt;), and I could super easily place a cheap domain in front of it
to make it a little more appealing than the factory-default &lt;code class=&quot;verbatim&quot;&gt;nemin32.github.io&lt;/code&gt; address.&lt;/p&gt;&lt;p&gt;However, due to the, ehm, &lt;em&gt;recent trends&lt;/em&gt; and emboldened by
the &lt;a href=&quot;https://taggart-tech.com/migrate-to-codeberg/&quot;&gt;rallying
cries&lt;/a&gt; of the people who had enough, I too took the dive and moved my
&lt;a href=&quot;https://codeberg.org/Nemin&quot;&gt;presence&lt;/a&gt; over to Codeberg.
This, of course, meant my two websites had to move too. Thankfully the
process wasn&amp;#39;t particularly painful (the article linked above pretty
much gets you up to speed in 10-15 minutes) and so I quickly had
everything set up.&lt;/p&gt;&lt;p&gt;While I thought I was done, sadly I quickly realized that though the
two services were similar in design, their quality of service was very
different. Codeberg Pages really struggled at times. My pages are rarely
over a couple hundred kilobytes (most of which being a couple
AVIF-encoded pictures) and yet there were times where loading my sites
took 10-15 seconds, while the browser just hanged.&lt;/p&gt;&lt;p&gt;Before anyone gets the wrong idea, I&amp;#39;m not blaming Codeberg for this
in any shape or form. In fact I&amp;#39;m very grateful, that the option exists
in the first place. After all, they are a (relatively) small non-profit,
who have to serve not just my silly little webpages, but sites for far
more serious projects too. With this in mind, it&amp;#39;s no wonder they can&amp;#39;t
always afford the computing power for me.&lt;/p&gt;&lt;p&gt;However, as much as they have my sympathies, I was still stuck with
this problem and had to figure out some solution. And said solution was
joining something useful with something pleasant: Enter VPS-es.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;1.1&lt;/span&gt; Picking my provider&lt;/h2&gt;&lt;p&gt;A VPS is a managed virtual machine, that one may rent from cloud
provider companies. The userbase is very wide, you can find people using
them from all areas of life, from students and hobbyists, to very
serious and large companies, who don&amp;#39;t want to bother with on-prem
solutions.&lt;/p&gt;&lt;p&gt;These days, if one happens to fall into the former category and only
really needs a machine that has some minimal RAM and a reasonably fast
few (perhaps even one) core CPU, they have a plethora of providers to
pick from, who specialize in pushing down prices and lending you such
VMs for literal peanuts.&lt;/p&gt;&lt;figure&gt;
&lt;img src=&quot;https://nemin.hu/vps/deals.avif&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;
&lt;figcaption&gt;An example of a &lt;a href=&quot;https://lowend-deals.xbit.win/&quot;&gt;site&lt;/a&gt; that collects such cheap
deals.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;For a striking example, just take a look at &lt;a href=&quot;https://lowendbox.com/blog/1-vps-1-usd-vps-per-month/&quot;&gt;LowEndBox&amp;#39;s
$1 per month VPS list&lt;/a&gt;, the name tells you what to expect. But if
you&amp;#39;re willing to shop around for more obscure providers and can contort
your needs even further (for instance by not requiring an IPv4 address),
I&amp;#39;ve found a &lt;a href=&quot;https://web.c-servers.co.uk/category/welcome-to-c-servers&quot;&gt;company&lt;/a&gt;,
who&amp;#39;ll rent you a VM with 5Gb of storage and cca. 400MB of RAM for $4.5
per year, not month, &lt;em&gt;year.&lt;/em&gt; I don&amp;#39;t think you can go much lower
than that.&lt;/p&gt;&lt;p&gt;However, figuring that it&amp;#39;s not like I&amp;#39;ll be spending fortunes either
way, I decided to stick to a well-known and trusted option, &lt;a href=&quot;https://www.netcup.com/en&quot;&gt;netcup&lt;/a&gt;. While their cheapest option
is closer to 2€ a month, in return you get an unmetered, 100Mbps
connection (as long as you use it in good faith), 30Gb of storage space,
the option to make a snapshot of your VM for backups, and the assurance
that your money is going to a bunch who know their stuff.&lt;/p&gt;&lt;p&gt;Sounds all great… if only it wasn&amp;#39;t so unexpectedly convoluted to do
business with them.&lt;/p&gt;&lt;h1&gt;&lt;span&gt;2&lt;/span&gt; German bureaucracy&lt;/h1&gt;&lt;p&gt;As explained earlier, my choice fell on the &lt;a href=&quot;https://www.netcup.com/en/server/vps/vps-pico-g11s-iv-12m-nue&quot;&gt;VPS
Pico G11s&lt;/a&gt;. At first the process was as one would expect:&lt;/p&gt;&lt;p&gt;I put the VPS in my cart, entered my address and personal data, then
clicked through the pages, until I noticed that there is a Netcup
newsletter and if you sign up for it, you get a voucher for 5€ off. I
figured &amp;quot;Hell yeah, that&amp;#39;s almost three months&amp;quot; and signed up. The
voucher arrived almost instantly and I happily saw that after entering
it, the price of the VPS dropped from 24€ to around 19€. Everything
seemed fine, so I pressed on the big, friendly &amp;quot;Order&amp;quot; button…&lt;/p&gt;&lt;p&gt;And was thrown back with an error.&lt;/p&gt;&lt;p&gt;Apparently the VPS I picked was too cheap for the voucher to be
applicable for, so I begrudgingly removed it and proceeded with the full
price. At the end of the day, 24€ is the cost of two fancy lunches, so I
wasn&amp;#39;t going to throw everything away just because I couldn&amp;#39;t be even
more of a cheapskate.&lt;/p&gt;&lt;p&gt;Having corrected my deal and pressed &amp;quot;Order&amp;quot; again, I expected to be
redirected to a payment portal. Instead, I was simply informed that an
actual human being will verify that my order is legit and that I must
wait until that happens. Considering this was very late in the afternoon
(way past the usual European working hours), I sighed and resigned
myself to wait until the next day or however long it&amp;#39;d take.&lt;/p&gt;&lt;p&gt;In the meantime I received an email, which I found really
interesting. It was written in a very serious and legalese way, with
long paragraphs of dry text describing my rights and responsibilities,
and that, by placing an order, I now have two weeks to refuse entering
into the contract with Netcup, after which it becomes binding.&lt;/p&gt;&lt;p&gt;I found this both quite refreshing, a lot of companies hide their
nasty details deep into Terms of Uses or other such texts, but not here.
Sure, it makes the communication towards their customer less bombastic,
but I absolutely didn&amp;#39;t mind that.&lt;/p&gt;&lt;p&gt;Then, to my biggest surprise, not ten minutes later my inbox pinged
me again: My inspection was successfully finished and I&amp;#39;m ready to log
into the portal. So I did just that, expecting to be finally able to
pay… only to be hit with a huge interface, where I was ordered to
declare my handling of user data according to GDPR.&lt;/p&gt;&lt;p&gt;Sadly, I didn&amp;#39;t make a screenshot of this and I&amp;#39;m no longer able to
access it, but more or less I was just asked what sort of data I intend
to handle. Considering I was making a simple static site, I declared
that I&amp;#39;m not handling any personal data and that was that.&lt;/p&gt;&lt;p&gt;At this point, finally, I was able to add my credit card and pay for
my server. Five minutes and another two emails later, I received a link
to &lt;em&gt;yet another completely different page,&lt;/em&gt; which (to my great
relief) did indeed allow me to access my machine.&lt;/p&gt;&lt;h1&gt;&lt;span&gt;3&lt;/span&gt; Setting up my VPS&lt;/h1&gt;&lt;p&gt;The VM came with Debian 13, which is a fine system otherwise, but
having had a recent interest in the various BSDs and stumbling on &lt;a href=&quot;https://httpd.rocks/&quot;&gt;httpd.rocks&lt;/a&gt; through &lt;a href=&quot;https://lobste.rs&quot;&gt;Lobste.rs&lt;/a&gt;, I decided to ditch it and try my
luck with OpenBSD 7.9 instead.&lt;/p&gt;&lt;figure&gt;
&lt;img src=&quot;https://nemin.hu/vps/images.avif&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;
&lt;figcaption&gt;A list of images provided by Netcup. All the BSDs and some
nice distro choices… also Windows, I guess, if you&amp;#39;re into that….
&lt;em&gt;weirdo&lt;/em&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;To my delight netcup provides the newest OpenBSD ISO on their
interface. One simply just has to click on it and the VM automatically
gets the disk loaded and the boot order changed to boot from it.
Power-cycling the VM is a little slow (taking around an entire minute),
but I don&amp;#39;t expect to do it much, so whatever.&lt;/p&gt;&lt;p&gt;Installing OpenBSD was a breeze. Hands down one of the easiest
processes when it comes to putting an OS on your machine. I disabled the
X windowing sets and the games set, but kept the rest. Apparently this
is a bit of a &lt;a href=&quot;https://www.bsdhowto.ch/instsets.html&quot;&gt;faux
pas&lt;/a&gt;, but worst case I&amp;#39;ll just reinstall the machine, no biggie.&lt;/p&gt;&lt;figure&gt;
&lt;img src=&quot;https://nemin.hu/vps/filesystem.avif&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;
&lt;figcaption&gt;The default filesystem I went with. &lt;code class=&quot;verbatim&quot;&gt;/var&lt;/code&gt; is woefully small.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;The only true error I think I made during installation was accepting
the default partitioning scheme, which wastes around 6GB of storage on
&lt;code class=&quot;verbatim&quot;&gt;/home&lt;/code&gt;, which I won&amp;#39;t be using anyway,
while &lt;code class=&quot;verbatim&quot;&gt;/var&lt;/code&gt; (arguably my most important
folder, as it contains the code for my website and my logs) ended up
being a measly 2GB. It is a bridge that I&amp;#39;ll eventually have to cross,
but for now it&amp;#39;s fine.&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note from the future:&lt;/strong&gt; I did end up reinstalling
while troubleshooting an unrelated issue and made &lt;code class=&quot;verbatim&quot;&gt;/var&lt;/code&gt; into a far bigger partition. Thankfully
with how fast and easy the installation of OpenBSD is, this was an
entirely painless process.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2&gt;&lt;span&gt;3.1&lt;/span&gt; SSH config&lt;/h2&gt;&lt;p&gt;While the Netcup interface does provide a remote desktop, it&amp;#39;s not
super pleasant to use. Not only is it excruciatingly slow, it is also
very particular about when it accepts input (you need to click into the
screen and it seems to occasionally lose focus on its own), so I quickly
decided to look into setting up SSH instead.&lt;/p&gt;&lt;p&gt;I didn&amp;#39;t have to look too far, &lt;a href=&quot;https://seeminglyrandom.net/posts/setup-openbsd-and-hardening-ssh/&quot;&gt;this
blogpost&lt;/a&gt; was perfect for my needs. It describes both how one can
bring a system up to speed and also how to set up SSH in a way that&amp;#39;s
secure, by disabling dubious ciphers.&lt;/p&gt;&lt;p&gt;I generated a brand new keypair for the VPS, because reusing the same
key for all your projects is a great way to be pwned. And after quickly
logging in with my password and issuing the right
&lt;code&gt;ssh-copy-id&lt;/code&gt; call, I disabled password login.&lt;/p&gt;&lt;p&gt;Afterwards I added the a new entry to my &lt;code class=&quot;verbatim&quot;&gt;~/.ssh/config&lt;/code&gt; file, so I only need to issue
&lt;code class=&quot;verbatim&quot;&gt;ssh vps&lt;/code&gt; and it handles the rest. Much more
handy than needing to do things manually:&lt;/p&gt;&lt;div&gt;
&lt;div&gt;
The first three lines aren&amp;#39;t strictly necessary, but they make long
connections a lot more tolerable.
&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;AddKeysToAgent yes
ServerAliveInterval 15
ServerAliveCountMax 3

Host vps
  HostName [IPv4 of the VPS]
  User [username]
  IdentityFile ~/.ssh/id_vps&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As a final touch, I symlinked &lt;code class=&quot;verbatim&quot;&gt;openrsync&lt;/code&gt;
to &lt;code class=&quot;verbatim&quot;&gt;rsync&lt;/code&gt;. This was necessary because my
main Linux machine&amp;#39;s &lt;code class=&quot;verbatim&quot;&gt;rsync&lt;/code&gt; expects to find
an executable with the same name on the VPS when I try to copy my files
over. Other than this hack, it&amp;#39;s all very clean with transparent SSH
handling. I can just do &lt;code class=&quot;verbatim&quot;&gt;rsync -av --delete source vps:destination&lt;/code&gt; and it
handles the rest.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;3.2&lt;/span&gt; Upgrading OpenBSD to
8.0-beta&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note from the future:&lt;/strong&gt; This section here is
anachronistic, as it&amp;#39;s placed before I discuss any of my server config,
but was actually written after I&amp;#39;ve finished and then completely rewrote
said config from scratch.&lt;/p&gt;
&lt;p&gt;The reason for this is that, while I originally intended to go with
the often-recommended &lt;code class=&quot;verbatim&quot;&gt;relayd&lt;/code&gt; to handle TLS
connections and attach HTTP headers to my responses (httpd.rocks has a
&lt;a href=&quot;https://httpd.rocks/relayd.html&quot;&gt;tutorial&lt;/a&gt; for this too!), I
stumbled upon &lt;a href=&quot;https://reddit.com/r/openbsd/comments/1vnn5ut/dumb_question_httpd_and_the_need_for_relayd/&quot;&gt;this&lt;/a&gt;
Reddit post while solving an unrelated issue, which mentioned that &lt;code class=&quot;verbatim&quot;&gt;httpd&lt;/code&gt; was soon to receive support for setting
HTTP headers itself (and it is already capable of handling TLS), making
&lt;code class=&quot;verbatim&quot;&gt;relayd&lt;/code&gt; pretty much overkill for my
purposes.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;It is considered both good practice and a painless way to enhance the
performance of your site to set a couple of HTTP headers that inform the
browser about how it may cache your content and that it should leak as
little info as possible.&lt;/p&gt;&lt;p&gt;While OpenBSD 7.9&amp;#39;s &lt;code class=&quot;verbatim&quot;&gt;httpd&lt;/code&gt; provides no
built-in mechanism for this (and thus requires an additional proxy
service), the (currently beta) 8.0 release &lt;a href=&quot;https://man.openbsd.org/httpd.conf#header&quot;&gt;does&lt;/a&gt; in the form of
the &lt;code&gt;header set&lt;/code&gt; directive.&lt;/p&gt;&lt;p&gt;To be able to use this, I had to upgrade my system from
&lt;code&gt;7.9-release&lt;/code&gt; to &lt;code&gt;8.0-beta&lt;/code&gt;. While this may sound
difficult at first glance, it turned out to be little more than issuing
&lt;code&gt;sysupgrade -s&lt;/code&gt;, waiting until the system downloaded the
newest OS release snapshot, and then automagically installed it, without
me having to touch anything at all.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;&lt;p&gt;After that I only had two more super easy housekeeping tasks to
do:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I needed to issue &lt;code&gt;pkg_add -u&lt;/code&gt; to upgrade all my
packages to their 8.0-compatible versions. This went through without
incident.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I also needed to call &lt;code&gt;sysmerge&lt;/code&gt; to update any system
configuration files that may have changed between the two releases. In
my case this was only &lt;code&gt;sshd_config&lt;/code&gt;, which introduced
post-quantum ciphers by default and changed &lt;code&gt;PermitRootLogin&lt;/code&gt;
to &lt;code&gt;prohibit-password&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As neither of these affected my usage of &lt;code&gt;sshd&lt;/code&gt;, I opted
to accept them without manual intervention.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;With the foundations done, it was time to actually have the machine
do its intended job. Frankly, however, any sort of detailed guide I
could write here would be a pale and imprecise imitation of httpd.rocks,
so I&amp;#39;ll keep things succinct and just comment on stuff I find
interesting to point out. If you find my journey at all inspiring, I
suggest checking the aforementioned guide out as well.&lt;/p&gt;&lt;p&gt;With this disclaimer out of the way, let&amp;#39;s get into it:&lt;/p&gt;&lt;h2&gt;&lt;span&gt;3.3&lt;/span&gt; Firewall&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;ext_if=&amp;quot;vio0&amp;quot;

set limit states 100000      # Maximum state table entries
set timeout interval 30      # Interval between state purges
set optimization &amp;quot;normal&amp;quot;    # General optimization for connection tracking

set skip on lo

# Antispoof
antispoof log quick for $ext_if
match in all scrub (no-df)

# Block all by default
block all   

# Block table for abusive hosts
table &amp;lt;block_table&amp;gt; persist file &amp;quot;/etc/pflists/block_table&amp;quot;
table &amp;lt;ssh_naughty&amp;gt; persist file &amp;quot;/etc/pflists/ssh_naughty&amp;quot;
block in quick from &amp;lt;block_table&amp;gt;
block in quick from &amp;lt;ssh_naughty&amp;gt;

# Block bogus destination addresses
table &amp;lt;martians&amp;gt; {
    0.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 \
    192.0.2.0/24 224.0.0.0/3 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 \
    203.0.113.0/24 \
    ::/128 ::/96 ::1/128 ::ffff:0:0/96 100::/64 2001:10::/28 2001:2::/48 \
    2001:db8::/32 3ffe::/16 fec0::/10 fc00::/7 }

block in quick on egress from any to &amp;lt;martians&amp;gt;

# Allow DNS and NTP
pass out on $ext_if proto {udp tcp} to any port 53
pass out on $ext_if proto udp to any port 123

# Allow SSH
pass in on $ext_if proto tcp from any to any port ssh synproxy state \
    (max-src-conn 10, max-src-conn-rate 5/30, overload &amp;lt;ssh_naughty&amp;gt; flush global)
pass out on $ext_if proto tcp from any to any port ssh keep state

# Allow web server stuff
pass in on $ext_if proto tcp from any to any port {80 443} synproxy state \
    (max-src-conn 100, max-src-conn-rate 30/5, overload &amp;lt;block_table&amp;gt; flush global)
pass out on $ext_if proto tcp from any to any port {80 443} keep state&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The firewall does the following:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Antispoofing and packet scrubbing,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;block all&lt;/code&gt; to be better safe than sorry,&lt;/li&gt;
&lt;li&gt;A blocklist to drop aggressive crawlers and one to SSH
brute-forcers,&lt;/li&gt;
&lt;li&gt;And allowing some select services through for basic functionality:
DNS, NTPD, SSH, and finally our webserver, with the latter two services
also having anti-flood measures enabled to put malicious clients on the
blocklists.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;While both blocklists forbid IPs on them from connecting, they have
one important difference:&lt;/p&gt;&lt;p&gt;I&amp;#39;ve set up my crontab so that it periodically calls
&lt;code&gt;pfctl -t block_table -T expire 8400&lt;/code&gt;. This command removes
clients that have been on the &lt;code&gt;block_table&lt;/code&gt; list for a while,
because it is entirely possible that it was merely a bumbling user, who
accidentally reloaded the page one too many times with their cache
turned off or that it was a fake IP address, that someone used.&lt;/p&gt;&lt;p&gt;The other list, &lt;code&gt;ssh_naughty&lt;/code&gt;, isn&amp;#39;t ever cleared. I only
intend to allow myself to log in, so if a client is hammering the VPS&amp;#39;
SSH daemon, then I cannot give it the same presumption of innocence.&lt;/p&gt;&lt;p&gt;And to make sure the list doesn&amp;#39;t disappear into smoke in case I
restart my system, I followed the advice of &lt;a href=&quot;https://www.bsdhowto.ch/savepftables.html&quot;&gt;this BSDHowTo
article&lt;/a&gt; and added the following into &lt;code class=&quot;verbatim&quot;&gt;/etc/rc.shutdown&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pfctl -t block_table -T show &amp;gt; /etc/pflists/block_table
pfctl -t ssh_naughty -T show &amp;gt; /etc/pflists/ssh_naughty
chmod 0600 /etc/pflists/block_table
chmod 0600 /etc/pflists/ssh_naughty&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This ensures that if my system goes down normally (i.e. for an update
or miscellaneous restart), the OS will automatically dump the
block-lists into files, which are then reloaded on the next startup by
the &lt;code class=&quot;verbatim&quot;&gt;file&lt;/code&gt; directive in &lt;code class=&quot;verbatim&quot;&gt;pf.conf&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;I&amp;#39;m considering also running this script as a cronjob in case of an
unexpected hard reset, but perhaps that&amp;#39;s not something one has to
actively worry about when it comes to VPS-es.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;3.4&lt;/span&gt; TLS certificates&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;authority letsencrypt {
    api url &amp;quot;https://acme-v02.api.letsencrypt.org/directory&amp;quot;
    account key &amp;quot;/etc/acme/letsencrypt-privkey.pem&amp;quot;
}

domain nemin.hu {
    alternative names { www.nemin.hu }
    domain key &amp;quot;/etc/ssl/private/nemin.hu.key&amp;quot;
    domain full chain certificate &amp;quot;/etc/ssl/nemin.hu.crt&amp;quot;
    sign with letsencrypt
}

domain oddwords.hu {
    alternative names { www.oddwords.hu }
    domain key &amp;quot;/etc/ssl/private/oddwords.hu.key&amp;quot;
    domain full chain certificate &amp;quot;/etc/ssl/oddwords.hu.crt&amp;quot;
    sign with letsencrypt
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I use OpenBSD&amp;#39;s built-in &lt;code&gt;acme-client(1)&lt;/code&gt;, which
interfaces with &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let&amp;#39;s Encrypt&lt;/a&gt; to
get TLS certificates for my two sites.&lt;/p&gt;&lt;p&gt;One thing I noticed, while still messing with my config is that
having two (or more) sites on one host makes troubleshooting certificate
issues quite tricky, because browsers will &lt;em&gt;helpfully&lt;/em&gt; try to use
your other site&amp;#39;s certificate instead, which will then cause a failure
as the browser realizes the name of the site you&amp;#39;re trying to reach and
the name stored in the certificate are completely different.&lt;/p&gt;&lt;p&gt;Therefore, if you&amp;#39;re facing issues while hosting multiple sites and
have the opportunity, I genuinely would suggest disabling all but the
problem server until you figure out the solution.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;3.5&lt;/span&gt; Web server&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;types { include &amp;quot;/usr/share/misc/mime.types&amp;quot; } 
prefork 10
no banner

#
# Nemin.hu
#

server &amp;quot;nemin.hu&amp;quot; {
    listen on * tls port 443
    root &amp;quot;/htdocs/nemin.hu&amp;quot;
    hsts {preload, subdomains}
    gzip-static
    log style combined

    location &amp;quot;/.well-known/acme-challenge/*&amp;quot; {
        root &amp;quot;/acme&amp;quot;
        request strip 2
    }

    # Drop malicious requests
    location &amp;quot;/.aws*&amp;quot; {block drop}
    location &amp;quot;/.env*&amp;quot; {block drop}
    location &amp;quot;/*.cgi*&amp;quot; {block drop}
    location &amp;quot;/cgi-bin/*&amp;quot; {block drop}
    location &amp;quot;/*.php*&amp;quot; {block drop}
    location &amp;quot;/*wp-*&amp;quot; {block drop}

    # Set headers
    header set &amp;quot;Cache-Control&amp;quot; &amp;quot;public, max-age=86400&amp;quot; always
    header set &amp;quot;X-Content-Type-Options&amp;quot; &amp;quot;nosniff&amp;quot; always
    header set &amp;quot;Referrer-Policy&amp;quot; &amp;quot;no-referrer&amp;quot; always
    header set &amp;quot;Permissions-Policy&amp;quot; &amp;quot;interest-cohort=()&amp;quot; always
    header set &amp;quot;X-Frame-Options&amp;quot; &amp;quot;SAMEORIGIN&amp;quot; always

    tls {
        certificate &amp;quot;/etc/ssl/nemin.hu.crt&amp;quot;
        key &amp;quot;/etc/ssl/private/nemin.hu.key&amp;quot;
    }
}
server &amp;quot;nemin.hu&amp;quot; {
    listen on * port 80

    location &amp;quot;/.well-known/acme-challenge/*&amp;quot; {
        root &amp;quot;/acme&amp;quot;
        request strip 2
    }

    # Drop malicious requests
    # ...

    # Set headers
    # ...

    block return 301 &amp;quot;https://nemin.hu$REQUEST_URI&amp;quot;
}
server &amp;quot;www.nemin.hu&amp;quot; {
    listen on * port 80
    listen on * tls port 443

    location &amp;quot;/.well-known/acme-challenge/*&amp;quot; {
        root &amp;quot;/acme&amp;quot;
        request strip 2
    }

    # Drop malicious requests
    # ...

    # Set headers
    # ...

    tls {
        certificate &amp;quot;/etc/ssl/nemin.hu.crt&amp;quot;
        key &amp;quot;/etc/ssl/private/nemin.hu.key&amp;quot;
    }

    block return 301 &amp;quot;https://nemin.hu$REQUEST_URI&amp;quot;
}

#
# Oddwords.hu
#

# More or less the same as above, just with the URL substituted...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code class=&quot;verbatim&quot;&gt;httpd&lt;/code&gt; config is fairly simple,
though it has a couple interesting notes I&amp;#39;ll lay out below.&lt;/p&gt;&lt;p&gt;The second and third block simply handle redirecting &lt;code class=&quot;verbatim&quot;&gt;www.&lt;/code&gt; and bare HTTP requests to &lt;code class=&quot;verbatim&quot;&gt;https://nemin.hu&lt;/code&gt;. All blocks are set up to
handle ACME challenges, because the client will try both
&lt;code&gt;nemin.hu&lt;/code&gt; and &lt;code&gt;www.nemin.hu&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The first block is the one that actually serves content. It&amp;#39;s also
very straightforward, but there are two calls that I think are worth
specifically mentioning:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code class=&quot;verbatim&quot;&gt;gzip-static&lt;/code&gt; allows your server to
send gzip-encoded HTML instead of uncompressed data. Notably it will
&lt;strong&gt;not&lt;/strong&gt; compress files itself, so you have to do that
yourself, but the &lt;a href=&quot;https://dataswamp.org/~solene/2022-04-22-openbsd-httpd-gzip.html&quot;&gt;blog
post&lt;/a&gt; where I learned about this feature also explains how easy it is
to create these archives yourselves. (It&amp;#39;s literally just calling &lt;code class=&quot;verbatim&quot;&gt;gzip -k&lt;/code&gt; on them.)&lt;/p&gt;
&lt;p&gt;And if that&amp;#39;s still too much manual work, &lt;a href=&quot;https://citizen428.net/blog/self-hosting-static-site-openbsd-httpd-relayd/&quot;&gt;this&lt;/a&gt;
other post describes a one-liner that allows you to recursively turn
everything (but images, which don&amp;#39;t benefit from compression) into a
&lt;code class=&quot;verbatim&quot;&gt;.gz&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;find public/ -type f ! -name &amp;#39;*.png&amp;#39; -exec gzip -9k &amp;quot;{}&amp;quot; \;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously you should adapt this to your needs. For instance, for me
&lt;code&gt;public/&lt;/code&gt; is &lt;code&gt;site/&lt;/code&gt; and instead of using PNG
files, I use AVIF (which also don&amp;#39;t really need zipping).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The lines starting with &lt;code class=&quot;verbatim&quot;&gt;location&lt;/code&gt;
match for some files crawlers like to frequent. Since this is a static
website, it&amp;#39;s not like they&amp;#39;d hit anything in the first place, however,
by silently dropping their connections, we waste their time and
resources.&lt;/p&gt;
&lt;p&gt;These filtering blocks are repeated everywhere, because I don&amp;#39;t want
to pointlessly redirect requests that will be dropped anyway.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, the lines starting with &lt;code class=&quot;verbatim&quot;&gt;header&lt;/code&gt; allow us to set HTTP headers on our
responses. This is the reason why I ugpraded to OpenBSD 8.0 earlier, as
7.9 doesn&amp;#39;t yet have this feature and would&amp;#39;ve required us to use &lt;code class=&quot;verbatim&quot;&gt;relayd&lt;/code&gt; or another proxy.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m not sure if it&amp;#39;s strictly necessary to include these headers on
calls that only redirect to the HTTPS URLs, but I figured it can&amp;#39;t hurt
and you only really have to set it up once.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;I kind of wish there was a more involved macro system for
&lt;code&gt;httpd.conf&lt;/code&gt;, that would allow shortening the block lists to
one definition that may be reused in all sections, but it&amp;#39;s not that
bad.&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note from the future:&lt;/strong&gt; This kept me up at night, so I
went and &lt;a href=&quot;https://nemin.hu/httpd-macros&quot;&gt;figured out a way&lt;/a&gt; to make things
terser.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h3&gt;&lt;span&gt;3.5.1&lt;/span&gt; Be careful with MIMEs&lt;/h3&gt;&lt;p&gt;Originally, when I first wrote my config, I didn&amp;#39;t include the first
line. None of the tutorials I&amp;#39;ve read mentioned it and the page loaded
just fine, so I figured all is well. Except everything wasn&amp;#39;t well,
because I noticed the pictures on the webpage loading incredibly slowly.
Up to 20-30 seconds, despite the fact that due to them being
highly-compressed AVIF images, they were rarely over 300KB in size.&lt;/p&gt;&lt;p&gt;A quick googling later I was able to pin down the problem: Without
informing the server about the various &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types&quot;&gt;MIME
types&lt;/a&gt;, it absolutely struggled to send over anything.&lt;/p&gt;&lt;p&gt;Fixing this is simply adding the following to your config outside any
of the server blocks:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;types { include &amp;quot;/usr/share/misc/mime.types&amp;quot; }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is where I&amp;#39;d like to repeat a very old and very true adage:
One should absolutely read the OpenBSD man pages. It is easy to skip
over them if one is used to Linux, because quite often the documentation
that comes with your packages might be sub-par or simply not detailed
enough to help you fix your issues without going on a wild goose chase
on the Internet.&lt;/p&gt;&lt;p&gt;However, with OpenBSD, this is different. The manual is super easy to
read and understand and even obscure things are often mentioned,
including the line seen above:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Types

   Configure the supported media types.  httpd(8) will set the Content-Type
   of the response header based on the file extension listed in the types
   section.  If not specified, httpd(8) will use built-in media types for
   text/css, text/html, text/plain, image/gif, image/png, image/jpeg,
   image/svg+xml, and application/javascript. The types section must
   include one or more lines of the following syntax, enclosed in curly
   braces:

   type/subtype name [name ...]
           Set the media type and subtype to the specified extension name.
           One or more names can be specified per line.  Each line may end
           with an optional semicolon.  Later lines overwrite earlier lines.

   include file
           Include types definitions from an external file, for example
           /usr/share/misc/mime.types.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see, the man page clearly states that AVIF files aren&amp;#39;t
included by default, so no wonder the server choked on them.&lt;/p&gt;&lt;p&gt;And with this line added, everything loads almost instantly.&lt;/p&gt;&lt;h1&gt;&lt;span&gt;4&lt;/span&gt; Findings; or, Bots Love Your
Obscure Content!&lt;/h1&gt;&lt;p&gt;While I was toying around with loading my site and checking the
access logs, I noticed that, beyond my own IP, there were a lot of other
clients connecting too. At first I was very happy to see this.
Considering I&amp;#39;ve never had analytics before, I had zero idea how many
people actually click on my pages, so seeing the buzzing activity felt
very validating…&lt;/p&gt;&lt;p&gt;That is until I actually read the user agents and realized about 40%
of these were AI scrapers, another 40% crawler bots, and only 20% were
clients I couldn&amp;#39;t decisively categorize as either.&lt;/p&gt;&lt;figure&gt;
&lt;img src=&quot;https://nemin.hu/vps/logs.avif&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;
&lt;figcaption&gt;An excerpt from my access logs. All of these were credential
scanner/stealer bots.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;I must admit this made me a little sad, but then I really shouldn&amp;#39;t
have expected anything different. What I found really amusing is that a
lot of these crawlers were trying to crack the site by scanning for
files such as &lt;code class=&quot;verbatim&quot;&gt;.aws&lt;/code&gt;, &lt;code class=&quot;verbatim&quot;&gt;.env&lt;/code&gt;, and &lt;code class=&quot;verbatim&quot;&gt;wp-*&lt;/code&gt;,
which they won&amp;#39;t find, because it&amp;#39;s a static site.&lt;/p&gt;&lt;p&gt;If you remember in the earlier section I added a bunch of lines
starting with &lt;code&gt;location&lt;/code&gt; in my &lt;code&gt;httpd.conf&lt;/code&gt; file.
This is why. When the crawlers try to read files matching these paths,
their requests will be silently dropped.&lt;/p&gt;&lt;p&gt;I also added a simple &lt;code class=&quot;verbatim&quot;&gt;robots.txt&lt;/code&gt;, that
disallows all the bigger AI crawlers from the site, but they don&amp;#39;t seem
to respect it. For now I&amp;#39;ve let things be, but perhaps eventually I&amp;#39;ll
add their user agents into the blocklist of my config.&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note from the future:&lt;/strong&gt; I&amp;#39;m adding this comment three
days later. My SSH blocklist already has 77 clients on it. The internet
is harsh.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h1&gt;&lt;span&gt;5&lt;/span&gt; Conclusion&lt;/h1&gt;&lt;figure&gt;
&lt;img src=&quot;https://nemin.hu/vps/term.avif&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;
&lt;figcaption&gt;I call my main machine &lt;code class=&quot;verbatim&quot;&gt;luna&lt;/code&gt;,
so calling a machine that&amp;#39;s both very far away and tiny &lt;code class=&quot;verbatim&quot;&gt;ceres&lt;/code&gt; felt very appropriate.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;My job title may technically be DevOps engineer, but I&amp;#39;m really more
of an &amp;quot;average&amp;quot; backend programmer and even when I do touch infra, it&amp;#39;s
a lot different to tweak tiny parts of an already well-established
stack, with several colleagues to help you and several backups to reach
for if something went wrong. So I went into this project with some
slight apprehension and with a feeling that any mistakes would leave me
to poke around in the dark.&lt;/p&gt;&lt;p&gt;However, reality turned out to be far less menacing. Migrating to a
VPS ended up much quicker and easier than I assumed and I also found it
an excellent learning experience, with plenty of further opportunities
to branch out to when I feel like it.&lt;/p&gt;&lt;p&gt;So if hosting your own site sounds exciting for you, nowadays it&amp;#39;s
easier than ever to try and it won&amp;#39;t cost you much.&lt;/p&gt;&lt;p&gt;Thanks for reading!&lt;/p&gt;&lt;h1&gt;&lt;span&gt;6&lt;/span&gt; Footnotes&lt;/h1&gt;&lt;section&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;From what I could find, the jury seems to be &lt;a href=&quot;https://dataswamp.org/~solene/2020-03-27-openbsd-current.html&quot;&gt;still&lt;/a&gt;
&lt;a href=&quot;https://lecorbeausvault.wordpress.com/2022/02/09/using-openbsd-current-is-easy-should-you/&quot;&gt;somewhat
out&lt;/a&gt; on whether it&amp;#39;s &amp;quot;safe&amp;quot; to use snapshots on a server.&lt;/p&gt;
&lt;p&gt;Proponents mention that the OS developers generally use the newest
available version and are forbidden from breaking it and also that by
using snapshots most of the obvious issues should be long ironed
out.&lt;/p&gt;
&lt;p&gt;Critics cite the need to update the system weekly or even more
frequently and the fact that, though rare, system breakages may still
happen a couple times a year.&lt;/p&gt;
&lt;p&gt;Considering my two sites aren&amp;#39;t mission critical in any shape or form
and that the VPS contains nothing that couldn&amp;#39;t be restored from a local
backup, I decided to go with it. If you&amp;#39;re more risk averse than me,
maybe wait until 8.0 drops, which should happen about 3 months after the
writing of this article (November of 2026).↩︎&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded>
</item>
<item>
<title>Spark Interact is now a Hostinger Partner – Spark Interact</title>
<link>https://www.sparkinteract.com.au/spark-interact-is-now-a-hostinger-partner/</link>
<enclosure type="image/jpeg" length="0" url="https://media.sparkinteract.com.au/2026/08/spark-hostinger-cover-1024x572.webp"></enclosure>
<guid isPermaLink="false">nncw2rcrGzmn09L6tIgIbp0hLAGxT7hUG9OiIw==</guid>
<pubDate>Tue, 25 Aug 2026 02:18:39 +0000</pubDate>
<description>Spark Interact has partnered with Hostinger, so our clients get better hosting options and 20% off plans. Here&#39;s what&#39;s included and how we choose hosting for a project.</description>
<content:encoded>&lt;p&gt;Good news for our clients: we can now offer you better hosting options.&lt;/p&gt;&lt;p&gt;We’ve partnered with Hostinger, which means we can match your website to hosting that suits your business, your traffic, and your budget. And when Hostinger is the right fit, you get 20% off the plan.&lt;/p&gt;&lt;h2&gt;What this means for you&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Real value in the plan.&lt;/strong&gt; Depending on the plan, Hostinger includes things you’d normally arrange separately: a free domain, business email, daily backups, and site migration. More of your budget goes into the website itself rather than the bill sitting underneath it.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;20% off, through our partner code.&lt;/strong&gt; That covers web hosting, cloud hosting, VPS, and Hostinger’s Agency Hosting tier. Higher discounts run during Hostinger’s campaign periods, so it’s worth a quick word with us before you buy.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Isolated environments and per site access.&lt;/strong&gt; This is the unglamorous part that matters. Your site sits in its own environment rather than sharing space with someone else’s, and we can give your team access to your site without handing over the keys to anything else.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Room to grow into.&lt;/strong&gt; The range runs from web hosting through to cloud and VPS, with options like dedicated IPs and daily backups higher up. If your traffic climbs, you’re moving up a tier rather than moving house.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;One point of contact.&lt;/strong&gt; You still deal with us. We handle the setup, the migration, and the ongoing management, and we’re the ones you call when something needs attention.&lt;/p&gt;&lt;h2&gt;Why we partner on our terms&lt;/h2&gt;&lt;p&gt;We look after websites for clients across construction, professional services, finance, and government-adjacent sectors. Those businesses have genuinely different requirements, and the right answer for one is rarely the right answer for all of them.&lt;/p&gt;&lt;p&gt;So we assess platforms carefully and partner with the ones that earn it. Hostinger did, and that’s why it’s now part of our stack.&lt;/p&gt;&lt;p&gt;The value in holding several strong options is simple: it means the recommendation you get is based on what your site actually needs, and we can say “this one, for these reasons” and mean it. There are more partnerships in progress, and we’ll share those as they land.&lt;/p&gt;&lt;h2&gt;How we choose hosting for our client&lt;/h2&gt;&lt;p&gt;“Which host is best” is the wrong question. The right one is “best for what”:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Traffic now, and traffic in eighteen months.&lt;/strong&gt; Hosting that suits a launch can throttle a business that grows into a real traffic load.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Where the data sits.&lt;/strong&gt; Relevant for anyone selling into government or enterprise, where data residency questions turn up in the procurement paperwork.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;How often the site is updated.&lt;/strong&gt; A site edited weekly needs staging and backups that are easy to reach. A site touched twice a year has different priorities.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Who’s maintaining it.&lt;/strong&gt; If your team is going to be in there, the control panel matters more than the raw specs.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Cost over three years, not month one.&lt;/strong&gt; Worth looking at the full picture before you commit.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Most of the time the answer is straightforward. Occasionally it saves a client a few thousand dollars, or saves them from a migration they never needed to do.&lt;/p&gt;&lt;h2&gt;Sign up and get your 20% off&lt;/h2&gt;&lt;p&gt;Two ways to claim it.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Use our partner link&lt;/strong&gt;, which applies the discount for you:&lt;br/&gt;&lt;a href=&quot;https://www.hostinger.com?REFERRALCODE=H2ISERVICCWZ&quot;&gt;https://www.hostinger.com?REFERRALCODE=H2ISERVICCWZ&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Or enter the code at checkout:&lt;/strong&gt; &lt;strong&gt;H2ISERVICCWZ&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Hostinger runs higher discounts during campaign periods, so if you’re not in a rush, check with us first and we’ll let you know if there’s a better offer running.&lt;/p&gt;&lt;p&gt;And if you’d rather we handled the setup, migration, and ongoing management, that’s what we normally do. Get in touch and we’ll work out together what suits the site you’re building.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Can a blog post be handwritten?</title>
<link>https://diggingforfire.blog/posts/can-a-blog-post-be-handwritten/</link>
<guid isPermaLink="false">lmUYkEGEiGx4l6AVRYE4V7B5UMpPctWBPwykng==</guid>
<pubDate>Mon, 24 Aug 2026 18:49:53 +0000</pubDate>
<description>██████████     ██████  ██████████ </description>
<content:encoded>&lt;body&gt;
	&lt;header&gt;
	&lt;pre&gt;		&lt;a href=&quot;https://diggingforfire.blog/&quot;&gt;
 ██████████     ██████  ██████████ 
&lt;span&gt;░░&lt;/span&gt;███&lt;span&gt;░░░░&lt;/span&gt;███   ███&lt;span&gt;░░░&lt;/span&gt;██&lt;span&gt;░░&lt;/span&gt;███&lt;span&gt;░░░░░&lt;/span&gt;  
 &lt;span&gt;░&lt;/span&gt;███   &lt;span&gt;░░&lt;/span&gt;███ &lt;span&gt;░&lt;/span&gt;███  &lt;span&gt;░░&lt;/span&gt;  &lt;span&gt;░&lt;/span&gt;███       
 &lt;span&gt;░&lt;/span&gt;███    &lt;span&gt;░&lt;/span&gt;███ ███████   &lt;span&gt;░&lt;/span&gt;███████   
 &lt;span&gt;░&lt;/span&gt;███    &lt;span&gt;░&lt;/span&gt;███&lt;span&gt;░░&lt;/span&gt;███&lt;span&gt;░░&lt;/span&gt;    &lt;span&gt;░&lt;/span&gt;███&lt;span&gt;░░░&lt;/span&gt;    
 &lt;span&gt;░&lt;/span&gt;███    ███  &lt;span&gt;░&lt;/span&gt;███      &lt;span&gt;░&lt;/span&gt;███       
 ██████████   &lt;span&gt;░&lt;/span&gt;███      █████      
&lt;span&gt;░░░░░░░░░░&lt;/span&gt;    &lt;span&gt;░░░&lt;/span&gt;      &lt;span&gt;░░░░░&lt;/span&gt;       
		&lt;/a&gt;
	&lt;/pre&gt;
	
	
&lt;/header&gt;

	
	&lt;main&gt;
		&lt;article&gt;
			&lt;h1&gt;Can a blog post be handwritten?&lt;/h1&gt;
			&lt;b&gt;&lt;time&gt;2026-08-20&lt;/time&gt;&lt;/b&gt;
			

			
			&lt;div&gt;
				&lt;div&gt;
  &lt;img src=&quot;https://diggingforfire.blog/images/can-a-blog-post-be-handwritten.webp&quot; alt=&quot;A handwritten version of this post.&quot; title=&quot;&quot;/&gt;

  &lt;pre&gt;Can a blog post be handwritten?
Suppose I handwrite a post on my
supernote, then export it as a
webp, which supports XMP
metadata. Pretty sure I should be
able to read that with javascript.
Then, I could embed the webp on
a blog post, and write a script
that will toggle between hand-
writing and the OCR&amp;#39;d (and edited)
text metadata.
Worth an experiment, I reckon.
Wonder if you can read my
handwriting, though.&lt;/pre&gt;

  
&lt;/div&gt;






			&lt;/div&gt;
		&lt;/article&gt;
	&lt;/main&gt;


	&lt;footer&gt;
	&lt;p&gt;© 2026 Digging for Fire
	| &lt;a href=&quot;https://diggingforfire.blog/blogroll/&quot;&gt;Blogroll&lt;/a&gt;
	| &lt;a href=&quot;https://diggingforfire.blog/podcast&quot;&gt;Podcast&lt;/a&gt;
	| &lt;a href=&quot;https://diggingforfire.blog/index.xml&quot;&gt;RSS&lt;/a&gt;
	| &lt;a href=&quot;https://buttondown.com/saadia&quot;&gt;Email&lt;/a&gt;
	| &lt;a href=&quot;https://diggingforfire.blog/socials/&quot;&gt;Socials&lt;/a&gt;
	&lt;/p&gt;
&lt;/footer&gt;




&lt;/body&gt;</content:encoded>
</item>
<item>
<title>SVG Filter Effects: Creating Texture with  | Codrops</title>
<link>https://tympanus.net/codrops/2019/02/19/svg-filter-effects-creating-texture-with-feturbulence/</link>
<enclosure type="image/jpeg" length="0" url="https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/02/SVGFilterEffects_feturbulance_featured.jpg?x57826"></enclosure>
<guid isPermaLink="false">gxKwUfdMwz0OexzrkxNA1tbAzwFnR6c1_t8KNg==</guid>
<pubDate>Mon, 24 Aug 2026 07:04:16 +0000</pubDate>
<description>feTurbulence is one of the most powerful SVG filter primitives. The specification defines this primitive as follows: Thi</description>
<content:encoded>&lt;div&gt;
              &lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/02/SVGFilterEffects_feturbulance_featured.jpg?x57826&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;          &lt;/div&gt;&lt;p&gt;&lt;code&gt;feTurbulence&lt;/code&gt; is one of the most powerful SVG filter primitives. The specification defines this primitive as follows:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;This filter primitive creates an image using the Perlin turbulence function. It allows the synthesis of artificial textures like clouds or marble. […]&lt;br/&gt;
The resulting image will fill the entire filter primitive subregion for this filter primitive.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;In other words, the &lt;code&gt;feTurbulence&lt;/code&gt; filter primitive generates and renders Perlin noise. This kind of noise is useful in simulating several natural phenomena like clouds, fire and smoke, and in generating complex texture like marble or granite. And like &lt;code&gt;feFlood&lt;/code&gt;, &lt;code&gt;feTurbulence&lt;/code&gt; &lt;strong&gt;fills the filter region&lt;/strong&gt; with new content.&lt;/p&gt;&lt;div&gt;
&lt;p&gt;This article is part of a series on SVG Filter effects. Check out the other articles in the series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/2019/01/15/svg-filters-101/&quot;&gt;SVG Filters 101&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/2019/01/22/svg-filter-effects-outline-text-with-femorphology/&quot;&gt;Outline Text with &amp;lt;feMorphology&amp;gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/2019/01/29/svg-filter-effects-poster-image-effect-with-fecomponenttransfer/&quot;&gt;Poster Image Effect with &amp;lt;feComponentTransfer&amp;gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/2019/02/05/svg-filter-effects-duotone-images-with-fecomponenttransfer/&quot;&gt;Duotone Images with &amp;lt;feComponentTransfer&amp;gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/2019/02/12/svg-filter-effects-conforming-text-to-surface-texture-with-fedisplacementmap/&quot;&gt;Conforming Text to Surface Texture with &amp;lt;feDisplacementMap&amp;gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Creating Texture with &amp;lt;feTurbulence&amp;gt; (this article)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/2019/02/26/svg-filter-effects-moving-forward/&quot;&gt;SVG Filter Effects: Moving Forward&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;&lt;p&gt;In this article, we’re going to go over how we can create noise with &lt;code&gt;feTurbulence&lt;/code&gt; and how that noise can be used to distort images and text, much like we did with the &lt;code&gt;feDisplacementMap&lt;/code&gt; texture in &lt;a href=&quot;https://tympanus.net/codrops/2019/02/12/svg-filter-effects-conforming-text-to-surface-texture-with-fedisplacementmap/&quot;&gt;the previous article&lt;/a&gt;. Then, we’re going to see how the generated noise can be used in combination with SVG lighting effects to create a simple rough paper texture.&lt;/p&gt;&lt;p&gt;But first, let’s get an overview of &lt;code&gt;feTurbulence&lt;/code&gt; and its attributes and see how each one affects the generated noise.&lt;/p&gt;&lt;h2&gt;Creating Turbulence and Fractal Noise with &lt;code&gt;feTurbulence&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;When I set out to write this series, I made the decision to avoid the gnarly technical details behind filter primitives as much as possible. This is why we won’t get into the technical details behind the functions used to generate &lt;a href=&quot;https://en.wikipedia.org/wiki/Perlin_noise&quot;&gt;Perlin noise&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After reading up on the function underlying noise generation, I found that it didn’t help me at all when I put the primitive into experimentation. After all, we &lt;em&gt;are&lt;/em&gt; working with a random noise generator here. So, most of the times, you’ll find that generating texture will be a matter of experimenting and tweaking until you get the desired result. With time, it gets a little easier to predict what a texture &lt;em&gt;might&lt;/em&gt; look like.&lt;/p&gt;&lt;p&gt;I’ve found that playing with &lt;code&gt;feTurbulence&lt;/code&gt; and tweaking its attributes &lt;em&gt;visually&lt;/em&gt; was the best way to learn about them and has helped me understand what each of the attributes does. So, we will be taking a visual approach to understanding &lt;code&gt;feTurbulence&lt;/code&gt;, with a few interactive demos to help.&lt;/p&gt;&lt;p&gt;Now, &lt;code&gt;feTurbulence&lt;/code&gt; generates noise using the &lt;a href=&quot;https://en.wikipedia.org/wiki/Perlin_noise&quot;&gt;Perlin Turbulence function&lt;/a&gt;. It has 5 main attributes that control the function and therefore the visual result of that function:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;baseFrequency&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numOctaves&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;seed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stitchTiles&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;We’ll go over how each of these attributes affects the visual result without going into the technical details of the function. You’ll find that, most of the times, you’ll only need to worry about three of these attributes: &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;baseFrequency&lt;/code&gt; and &lt;code&gt;numOctaves&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;&lt;code&gt;baseFrequency&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;In order to generate noise, only the &lt;code&gt;baseFrequency&lt;/code&gt; attribute is required. The &lt;code&gt;baseFrequency&lt;/code&gt; affects the size (or scale) and the grain of the generated noise.&lt;/p&gt;&lt;p&gt;&lt;code&gt;baseFrequency&lt;/code&gt;’s effect is best understood when it is visualized and animated. That’s why I created the following live demo. Using the slider, you can change the value of the base frequency used and see how it affects the generated noise in real-time. You’ll notice that as you increase or decrease the value of the &lt;code&gt;baseFrequency&lt;/code&gt; attribute, the generated pattern &lt;strong&gt;remains intact as it becomes smaller or larger&lt;/strong&gt;, respectively, and &lt;strong&gt;looks like it’s zooming in and out of its origin at the top left corner&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;
See the Pen feTurbluence: baseFrequency by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;p&gt;Lower &lt;code&gt;baseFrequency&lt;/code&gt; values (such as 0.001) generate larger patterns, while higher values (0.5+) produce smaller patterns. The values start from 0 (no frequency == no pattern) and up. Negative values are not allowed. &lt;a href=&quot;https://www.creativebloq.com/netmag/how-go-beyond-basics-svg-filters-71412280&quot;&gt;as Michael Mullany mentions&lt;/a&gt;, “&lt;em&gt;values in the 0.02 to 0.2 range are useful starting points for most textures.&lt;/em&gt;”&lt;/p&gt;&lt;p&gt;Note that the noise generated does not have a background color. Meaning that, if you remove the white background color on the SVG, you’ll be able to see the dark body’s background through the noise.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;baseFrequency&lt;/code&gt; attribute also accepts two values. When you provide two values, the first one will be used for the base frequency on the x-axis and the second one will correspond to the y-axis. By providing two different values, you can generate vertical or horizontal noise that can be used to achieve some fantastic effects, as we’re going to see in a following section.&lt;/p&gt;&lt;p&gt;Play with the values of the &lt;code&gt;baseFrequency&lt;/code&gt; again in this live demo and notice how it changes along the X and Y axes as you give it different values. The demo starts with a nice horizontal noise. The 0.01 x-baseFrequency value is relatively small, which makes the horizontal pattern larger (like it’s stretched out). If you decrease it further (to 0.001, for example), you’ll see the horizontal pattern become more like lines. Try it.&lt;/p&gt;&lt;p&gt;
See the Pen feTurbluence: x &amp;amp; y baseFrequency  by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;h3&gt;&lt;code&gt;type&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;As its name suggests, the &lt;code&gt;type&lt;/code&gt; attribute is used to specify the type of noise generated by &lt;code&gt;feTurbulence&lt;/code&gt;. There are two types available:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;turbulence&lt;/code&gt;, which is the default value, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fractalNoise&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;code&gt;fractalNoise&lt;/code&gt; generates a more cloudy and smooth pattern and is a suitable base for creating gas-base textures like clouds. &lt;code&gt;turbulence&lt;/code&gt; generates more lines that simulate ripples and are thus suitable as a base for liquid textures.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-20-at-16.55.25.png?x57826&quot; alt=&quot;Screen Shot 2019-01-20 at 16.55.25&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;turbulence type noise on the left, and fractalNoise type on the right.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Change the value of the &lt;code&gt;type&lt;/code&gt; attribute in the following demo to see how the generated pattern changes:&lt;/p&gt;&lt;p&gt;
See the Pen feTurbluence: stitchTiles by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;h3&gt;&lt;code&gt;numOctaves&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;numOctaves&lt;/code&gt; is short for the “number of octaves”, which represent the level of detail in a noise.&lt;/p&gt;&lt;p&gt;In music, an octave is the difference in pitch between two notes where one has twice the frequency of the other. So the higher the octaves, the higher the frequency. In &lt;code&gt;feTurbulence&lt;/code&gt;, the higher the number of octaves, the more detail you can see in the noise it generates. By default, the generated noise has one octave, which means that the default value for the &lt;code&gt;numOctaves&lt;/code&gt; attribute is 1.&lt;/p&gt;&lt;p&gt;Drag the slider in the following demo to see the effect of increasing the number of octaves on the generated texture:&lt;/p&gt;&lt;p&gt;
See the Pen feTurbluence: numOctaves by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;p&gt;You’ll notice that starting from &lt;code&gt;numOctaves=&amp;quot;5&amp;quot;&lt;/code&gt; the effect of adding more octaves becomes practically unnoticeable.&lt;/p&gt;&lt;h3&gt;&lt;code&gt;seed&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;seed&lt;/code&gt;, as &lt;a href=&quot;https://www.w3.org/TR/filter-effects-1/#feTurbulenceElement&quot;&gt;defined in the specification&lt;/a&gt;, is &lt;em&gt;“the starting number for the pseudo random number generator”&lt;/em&gt;. In other words, it provides a different starting number for the random function used to generated our random noise.&lt;/p&gt;&lt;p&gt;Visually, you’ll see that it affects where and how the “ripple lines” are generated. It is also better understood when you see how it affects the noise generated in two adjacent rectangles.&lt;/p&gt;&lt;p&gt;When the same &lt;code&gt;seed&lt;/code&gt; is used for the two adjacent rectangles, the function used to generate the noise across the two rectangles is &lt;strong&gt;continuous&lt;/strong&gt;, and this will be reflected visually by the continuity of the “ripple lines” across the edges of the two rectangles.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Group.png?x57826&quot; alt=&quot;Group&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;The continuity of the function generating the random noise can be seen along the edges of the two rectangles using the same &lt;code&gt;seed&lt;/code&gt; value.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Play with the value of the &lt;code&gt;seed&lt;/code&gt; attribute in the following demo, see how it affects the generated noise, and notice how the noise is continuous across the edges of the two rectangles that are using the same &lt;code&gt;seed&lt;/code&gt; value.&lt;/p&gt;&lt;p&gt;
See the Pen feTurbluence: seed by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;h3&gt;&lt;code&gt;stitchTiles&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;stitchTiles&lt;/code&gt; can be used to create a stitching effect between “tiles” of noise. The effect of this attribute is very similar to that of the &lt;code&gt;seed&lt;/code&gt;, meaning that it is most evident when you have two adjacent areas (or “tiles”) of noise.&lt;/p&gt;&lt;p&gt;As the specification mentions, sometimes the result of the noise generation will show clear discontinuities at the tile borders. You can tell the browser to try to smooth the results out so that the two tiles appear to be “stitched” together. (I really like how the attribute and its effect are compared to &lt;em&gt;stitching&lt;/em&gt;.)&lt;/p&gt;&lt;p&gt;By default, no attempt is made to achieve smooth transitions at the border of tiles which contain a turbulence function because the default value for &lt;code&gt;stitchTiles&lt;/code&gt; is &lt;code&gt;noStitch&lt;/code&gt;. If you want to create that stitching effect, you can change the value to &lt;code&gt;stitch&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;In order to compare the result of &lt;code&gt;stitchTiles&lt;/code&gt; to that of &lt;code&gt;seed&lt;/code&gt;, I have applied the same &lt;code&gt;seed&lt;/code&gt; value to the noise generated in the two rectangles in the following demo. You can already see that the noise appears to be continuous between the two. Switch the &lt;code&gt;stitchTiles&lt;/code&gt; option “on” (by changing its value to &lt;code&gt;stitch&lt;/code&gt;) to see how the noise changes to accommodate across the edges.&lt;/p&gt;&lt;p&gt;
See the Pen feTurbluence: stitchTiles by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;p&gt;As I mentioned earlier, the only three attributes you’ll most likely be using are &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;baseFrequency&lt;/code&gt; and &lt;code&gt;numOctaves&lt;/code&gt;. So we’ll be focusing on these three moving forward.&lt;/p&gt;&lt;h2&gt;Using &lt;code&gt;feTurbulence&lt;/code&gt;-Generated Noise to Distort Content&lt;/h2&gt;&lt;p&gt;&lt;em&gt;This&lt;/em&gt; is where the fun starts. And this is where we start putting the generated noise to use. After all, just filling the filter region with the noise has no use in and of itself.&lt;/p&gt;&lt;p&gt;In the &lt;a href=&quot;https://tympanus.net/codrops/2019/02/12/svg-filter-effects-conforming-text-to-surface-texture-with-fedisplacementmap/&quot;&gt;previous article&lt;/a&gt; we used &lt;code&gt;feDisplacementMap&lt;/code&gt; to conform a piece of text to the texture in an external image. And we mentioned that &lt;code&gt;feDisplacementMap&lt;/code&gt; uses the color information in one image to distort another. The image that is used as a displacement map can be &lt;em&gt;any&lt;/em&gt; image. This means that it can be an external image or an image generated within SVG, such as a gradient image or a pattern… or a noise texture.&lt;/p&gt;&lt;p&gt;In other words, the noise we generate with &lt;code&gt;feTurbulence&lt;/code&gt; can as well be used to distort content if it is used with &lt;code&gt;feDisplacementMap&lt;/code&gt;. In the following demo, we used the output of &lt;code&gt;feTurbulence&lt;/code&gt; to displace the image with &lt;code&gt;feDisplacementMap&lt;/code&gt;. I’m using a horizontal noise pattern by providing two different values for the &lt;code&gt;baseFrequency&lt;/code&gt; attribute similar to what we did earlier.&lt;/p&gt;&lt;pre&gt;&amp;lt;svg viewBox=&amp;quot;0 0 180 100&amp;quot;&amp;gt;
    &amp;lt;filter id=&amp;quot;noise&amp;quot; x=&amp;quot;0%&amp;quot; y=&amp;quot;0%&amp;quot; width=&amp;quot;100%&amp;quot; height=&amp;quot;100%&amp;quot;&amp;gt;
        &amp;lt;feTurbulence baseFrequency=&amp;quot;0.01 0.4&amp;quot; result=&amp;quot;NOISE&amp;quot; numOctaves=&amp;quot;2&amp;quot; /&amp;gt;
        &amp;lt;feDisplacementMap in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;NOISE&amp;quot; scale=&amp;quot;20&amp;quot; xChannelSelector=&amp;quot;R&amp;quot; yChannelSelector=&amp;quot;R&amp;quot;&amp;gt;&amp;lt;/feDisplacementMap&amp;gt;
    &amp;lt;/filter&amp;gt;

    &amp;lt;image xlink:href=&amp;quot;...&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; width=&amp;quot;100%&amp;quot; height=&amp;quot;100%&amp;quot; filter=&amp;quot;url(#noise)&amp;quot;&amp;gt;&amp;lt;/image&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/pre&gt;&lt;p&gt;
See the Pen feTurbluence as a displacementMap by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;p&gt;The intensity by which the turbulence distorts the image is specified in the &lt;code&gt;scale&lt;/code&gt; attribute on &lt;code&gt;feDisplacementMap&lt;/code&gt;. I’ve used a large value so that the effect looks more dramatic.&lt;/p&gt;&lt;p&gt;Now, going from this simple application, we can open a lot more possibilities when we combine the facts that:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;SVG filters can be applied to HTML content, and&lt;/li&gt;
&lt;li&gt;the values of &lt;code&gt;baseFrequency&lt;/code&gt; are &lt;em&gt;numbers&lt;/em&gt; and can thus be animated..&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;A little less than a couple of years ago, &lt;a href=&quot;https://tympanus.net/codrops/author/grsmto/&quot;&gt;Adrien Denat&lt;/a&gt; wrote &lt;a href=&quot;https://tympanus.net/codrops/2016/05/11/distorted-button-effects-with-svg-filters/&quot;&gt;an article right here on Codrops&lt;/a&gt; in which he experimented with a similar effect applied to HTML &lt;code&gt;button&lt;/code&gt;s. We’re going to break down and recreate the following button click effect:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/b7w-1.gif?x57826&quot; alt=&quot;b7w&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;We’re going to start by creating the noise texture. We’re going to start with the final state—the state where the button is distorted, and then, once we’ve got that, we’re going to animate the initial state of the button to that distorted state and back on click.&lt;/p&gt;&lt;p&gt;Our aim here is to distort the button horizontally. So we will be using and tweaking the horizontal noise from the previous demo a little bit. Its distortion effect on the image is a little too strong, so I’m going to dial it down first by changing the turbulence value from &lt;code&gt;0.01 0.4&lt;/code&gt; to &lt;code&gt;0 0.2&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&amp;lt;filter id=&amp;#39;noise&amp;#39; x=&amp;#39;0%&amp;#39; y=&amp;#39;0%&amp;#39; width=&amp;#39;100%&amp;#39; height=&amp;#39;100%&amp;#39;&amp;gt;
        &amp;lt;feTurbulence type=&amp;quot;turbulence&amp;quot; baseFrequency=&amp;quot;0 0.2&amp;quot; result=&amp;quot;NOISE&amp;quot; numOctaves=&amp;quot;2&amp;quot; /&amp;gt;
        &amp;lt;feDisplacementMap in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;NOISE&amp;quot; scale=&amp;quot;30&amp;quot; xChannelSelector=&amp;quot;R&amp;quot; yChannelSelector=&amp;quot;R&amp;quot;&amp;gt;&amp;lt;/feDisplacementMap&amp;gt;
&amp;lt;/filter&amp;gt;
&lt;/pre&gt;&lt;p&gt;The effect gets a little better, but the button is still distorted more than we’d like it to:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-10.40.52.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 10.40.52&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;We want the distortion to be less dramatic. A useful tip to keep in mind is that &lt;strong&gt;we can dial the effect of the noise down instantly by switching the type of noise from the default &lt;code&gt;turbulence&lt;/code&gt; to the smoother &lt;code&gt;fractalNoise&lt;/code&gt;.&lt;/strong&gt; As soon we do that, we can see that the distortion effect has also been “smoothed” down:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-10.44.20.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 10.44.20&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;This looks much better.&lt;/p&gt;&lt;p&gt;Now that we’ve got a distortion effect we’re happy with, we will start our demo with a filter that, initially, does practically nothing:&lt;/p&gt;&lt;pre&gt;&amp;lt;filter id=&amp;#39;noise&amp;#39; x=&amp;#39;0%&amp;#39; y=&amp;#39;0%&amp;#39; width=&amp;#39;100%&amp;#39; height=&amp;#39;100%&amp;#39;&amp;gt;
        &amp;lt;feTurbulence &lt;strong&gt;type=&amp;quot;fractalNoise&amp;quot; baseFrequency=&amp;quot;0 0.000001&amp;quot;&lt;/strong&gt; result=&amp;quot;NOISE&amp;quot; numOctaves=&amp;quot;2&amp;quot; /&amp;gt;
        &amp;lt;feDisplacementMap in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;NOISE&amp;quot; scale=&amp;quot;30&amp;quot; xChannelSelector=&amp;quot;R&amp;quot; yChannelSelector=&amp;quot;R&amp;quot;&amp;gt;&amp;lt;/feDisplacementMap&amp;gt;
&amp;lt;/filter&amp;gt;
&lt;/pre&gt;&lt;p&gt;We’re going to apply that filter to our &lt;code&gt;button&lt;/code&gt; in CSS:&lt;/p&gt;&lt;pre&gt;button {
    -webkit-filter: url(#noise);
            filter: url(#noise);
}
&lt;/pre&gt;&lt;p&gt;At this point, the button still looks un-distorted.&lt;/p&gt;&lt;p&gt;Next, we’re going to use (a slightly modified version of) Adrien’s code which uses &lt;a href=&quot;https://greensock.com&quot;&gt;GSAP to animate the value inside &lt;code&gt;feTurbulence&lt;/code&gt;’s &lt;code&gt;baseFrequency&lt;/code&gt; to &lt;code&gt;0 0.2&lt;/code&gt; and back on click:&lt;/a&gt;&lt;/p&gt;&lt;pre&gt;var bt = document.querySelectorAll(&amp;#39;.button&amp;#39;)[0],
	turbVal = { val: 0.000001 },
	turb = document.querySelectorAll(&amp;#39;#noise feTurbulence&amp;#39;)[0],
	
	btTl = new TimelineLite({ paused: true, onUpdate: function() {
  turb.setAttribute(&amp;#39;baseFrequency&amp;#39;, &amp;#39;0 &amp;#39; + turbVal.val);
} });

btTl.to(turbVal, 0.2, { val: 0.2 })
    .to(turbVal, 0.2, { val: 0.000001 });

bt.addEventListener(&amp;#39;click&amp;#39;, function() {
  btTl.restart();
});
&lt;/pre&gt;&lt;p&gt;And that’s all there is to it, really. You can play with the live demo here:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://greensock.com&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://greensock.com&quot;&gt;
See the Pen feTurbluence on BUTTONs by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/a&gt;&lt;/p&gt;&lt;a href=&quot;https://greensock.com&quot;&gt;

&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://greensock.com&quot;&gt;The demo works in Chrome and Firefox at the time of writing of this article. It is buggy in the current version of Safari but the issue is resolved in the next version, as the Safari Tech Preview shows the demo works perfectly. It doesn’t work in MS Edge, though, &lt;em&gt;but&lt;/em&gt; the button isn’t distorted at all which means that the lack of support does not affect the usability of the button. This is great because &lt;strong&gt;you can still use this effect as an enhancement.&lt;/strong&gt;If the effect isn’t supported, the button will simply look and behave like a normal, effect-less button. Adrien’s article includes quite &lt;/a&gt;&lt;a href=&quot;https://tympanus.net/Development/DistortedButtonEffects/&quot;&gt;a few more button distortion effects&lt;/a&gt; that use the same principles we’ve just covered that are definitely worth checking out and breaking down. There are one or two nice tricks to learn from each.&lt;/p&gt;&lt;h3&gt;Squiggly Text using &lt;code&gt;feTurbulence&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;One of my favorite examples of &lt;code&gt;feTurbulence&lt;/code&gt; in action is Lucas Bebber’s &lt;a href=&quot;https://codepen.io/lbebber/pen/KwGEQv&quot;&gt;Squiggly Text&lt;/a&gt; effect. In his demo, Lucas is using multiple &lt;code&gt;feTurbulence&lt;/code&gt; functions:&lt;/p&gt;&lt;pre&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; version=&amp;quot;1.1&amp;quot;&amp;gt;
    &amp;lt;defs&amp;gt;
        &amp;lt;filter id=&amp;quot;squiggly-0&amp;quot;&amp;gt;
            &amp;lt;feTurbulence id=&amp;quot;turbulence&amp;quot; baseFrequency=&amp;quot;0.02&amp;quot; numOctaves=&amp;quot;3&amp;quot; result=&amp;quot;noise&amp;quot; seed=&amp;quot;0&amp;quot; /&amp;gt;
            &amp;lt;feDisplacementMap id=&amp;quot;displacement&amp;quot; in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;noise&amp;quot; scale=&amp;quot;6&amp;quot; /&amp;gt;
        &amp;lt;/filter&amp;gt;
        &amp;lt;filter id=&amp;quot;squiggly-1&amp;quot;&amp;gt;
            &amp;lt;feTurbulence id=&amp;quot;turbulence&amp;quot; baseFrequency=&amp;quot;0.02&amp;quot; numOctaves=&amp;quot;3&amp;quot; result=&amp;quot;noise&amp;quot; seed=&amp;quot;1&amp;quot; /&amp;gt;
            &amp;lt;feDisplacementMap in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;noise&amp;quot; scale=&amp;quot;8&amp;quot; /&amp;gt;
        &amp;lt;/filter&amp;gt;

        &amp;lt;filter id=&amp;quot;squiggly-2&amp;quot;&amp;gt;
            &amp;lt;feTurbulence id=&amp;quot;turbulence&amp;quot; baseFrequency=&amp;quot;0.02&amp;quot; numOctaves=&amp;quot;3&amp;quot; result=&amp;quot;noise&amp;quot; seed=&amp;quot;2&amp;quot; /&amp;gt;
            &amp;lt;feDisplacementMap in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;noise&amp;quot; scale=&amp;quot;6&amp;quot; /&amp;gt;
        &amp;lt;/filter&amp;gt;
        &amp;lt;filter id=&amp;quot;squiggly-3&amp;quot;&amp;gt;
            &amp;lt;feTurbulence id=&amp;quot;turbulence&amp;quot; baseFrequency=&amp;quot;0.02&amp;quot; numOctaves=&amp;quot;3&amp;quot; result=&amp;quot;noise&amp;quot; seed=&amp;quot;3&amp;quot; /&amp;gt;
            &amp;lt;feDisplacementMap in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;noise&amp;quot; scale=&amp;quot;8&amp;quot; /&amp;gt;
        &amp;lt;/filter&amp;gt;

        &amp;lt;filter id=&amp;quot;squiggly-4&amp;quot;&amp;gt;
            &amp;lt;feTurbulence id=&amp;quot;turbulence&amp;quot; baseFrequency=&amp;quot;0.02&amp;quot; numOctaves=&amp;quot;3&amp;quot; result=&amp;quot;noise&amp;quot; seed=&amp;quot;4&amp;quot; /&amp;gt;
            &amp;lt;feDisplacementMap in=&amp;quot;SourceGraphic&amp;quot; in2=&amp;quot;noise&amp;quot; scale=&amp;quot;6&amp;quot; /&amp;gt;
        &amp;lt;/filter&amp;gt;
    &amp;lt;/defs&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/pre&gt;&lt;p&gt;..and applying them via CSS to a piece of HTML text using CSS animations, animating from one to another:&lt;/p&gt;&lt;pre&gt;@keyframes squiggly-anim {
  0% {
    -webkit-filter: url(&amp;quot;#squiggly-0&amp;quot;);
            filter: url(&amp;quot;#squiggly-0&amp;quot;);
  }
  25% {
    -webkit-filter: url(&amp;quot;#squiggly-1&amp;quot;);
            filter: url(&amp;quot;#squiggly-1&amp;quot;);
  }
  50% {
    -webkit-filter: url(&amp;quot;#squiggly-2&amp;quot;);
            filter: url(&amp;quot;#squiggly-2&amp;quot;);
  }
  75% {
    -webkit-filter: url(&amp;quot;#squiggly-3&amp;quot;);
            filter: url(&amp;quot;#squiggly-3&amp;quot;);
  }
  100% {
    -webkit-filter: url(&amp;quot;#squiggly-4&amp;quot;);
            filter: url(&amp;quot;#squiggly-4&amp;quot;);
  }
}
&lt;/pre&gt;&lt;p&gt;..thus creating the squiggly effect.&lt;/p&gt;&lt;p&gt;Once again, the text used is real, which means that it is searchable, selectable, accessible and editable (using the &lt;code&gt;contenteditable&lt;/code&gt; attribute). &lt;a href=&quot;https://codepen.io/lbebber/pen/KwGEQv?editors=1100&quot;&gt;Check the live demo out&lt;/a&gt;, but beware that this demo is resource-intensive, so you may want to avoid opening the Codepen on mobile.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/02/b7w.gif?x57826&quot; alt=&quot;An animated screenshot of Lucas’s squiggly text demo.&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;So, some &lt;strong&gt;useful takeaways&lt;/strong&gt; from this section are:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;The noise generated using &lt;code&gt;feTurbulence&lt;/code&gt; can be used to distort both SVG and HTML content.&lt;/li&gt;
&lt;li&gt;The value of &lt;code&gt;baseFrequency&lt;/code&gt; can be animated.&lt;/li&gt;
&lt;li&gt;You can dial the amount of distortion down by tweaking the values in &lt;code&gt;baseFrequency&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; by smoothing the noise out with the &lt;code&gt;fractalNoise&lt;/code&gt; type.&lt;/li&gt;
&lt;li&gt;Even though you can animate SVG filters in general, it’s usually recommended to not overdo it because they can be quite resource-intensive. Try to keep the animations limited to smaller areas; the larger the animated area, the more resource-consuming it will be.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;code&gt;feTurbulence&lt;/code&gt; is rarely—if ever—useful when used alone. It is pretty much always used by (an)other filter primitive(s) to achieve particular effects. In this section, we used it as a displacement map in &lt;code&gt;feDisplacementMap&lt;/code&gt;. Let’s see what more we can do with it.&lt;/p&gt;&lt;h2&gt;Simulating Natural Texture with &lt;code&gt;feTurbulence&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;Another useful way &lt;code&gt;feTurbulence&lt;/code&gt;-generated noise can be used is to &lt;strong&gt;simulate natural texture&lt;/strong&gt;. If you’ve ever used the noise generation plugins in After Effects, you may have already come across this functionality and examples of doing so.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-11.09.05.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 11.09.05&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;Examples of textures created in After Effects using the Fractal Noise plug-in. &lt;a href=&quot;https://library.creativecow.net/articles/zwar_chris/fractal_noise.php&quot;&gt;(Source)&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;code&gt;feTurbulence&lt;/code&gt; generates noise (random values) across each of the R, G, B, and A components. You can tweak the values for each of these components to get different variations of the noise. In order to simulate a texture, we usually need to do exactly that: tweak the R/G/B/A components (canceling out components, saturating others, etc.) to get our desired result. Other times, all we need to do is shed some light on it. Literally.&lt;/p&gt;&lt;p&gt;In this section, we’re going to break down a &lt;a href=&quot;https://www.creativebloq.com/netmag/how-go-beyond-basics-svg-filters-71412280&quot;&gt;rough paper texture effect created by Michael Mullany&lt;/a&gt;. In order to create this texture, we will need to shine a light on a noise texture generated by &lt;code&gt;feTurbulence&lt;/code&gt; using SVG’s lighting sources.&lt;/p&gt;&lt;h3&gt;Lighting Sources in SVG&lt;/h3&gt;&lt;p&gt;SVG conveniently provides a few primitives that can be used to shine a light on objects or images.&lt;/p&gt;&lt;p&gt;There are two filter primitives that are used to specify &lt;strong&gt;the type of light&lt;/strong&gt; you want:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feDiffuseLighting&lt;/code&gt; which indicates indirect light from an outside source, and is best used for sunlight effects, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feSpecularLighting&lt;/code&gt; which specifies secondary light that bounced from reflective surfaces.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Both primitives shine a light on an object or image by &lt;strong&gt;using the alpha channel of that image as a bump map&lt;/strong&gt;. Transparent values remain flat, while opaque values rise to form peaks that are illuminated more prominently.&lt;/p&gt;&lt;p&gt;In other words, a light source filter uses an input’s alpha channel to provide depth information: higher opacity areas are raised toward the viewer and lower opacity areas recede away from the viewer. This means that the alpha value of a pixel in the input is used as the height of that pixel in the z-dimension, and the filter uses that height to calculate a virtual surface, which will reflect a particular amount of light from the light source. (This is pretty powerful stuff!)&lt;/p&gt;&lt;p&gt;Both types of light accept an attribute called &lt;code&gt;surfaceScale&lt;/code&gt; which is practically a z-index multiplier. If you increase this value, the “slopes” in the surface texture become steeper.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“Because &lt;code&gt;feTurbulence&lt;/code&gt; generates an alpha channel full of noisy values from 0 to 1, it produces a nice variable Z terrain that creates highlights when we shine our light on it.” —Michael Mullany&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;After deciding on the &lt;em&gt;type&lt;/em&gt; of light you need, you’ll want to choose a light &lt;em&gt;source&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;There are &lt;strong&gt;three kinds of light sources&lt;/strong&gt; in SVG:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;feDistantLight&lt;/code&gt;: this represents a distant light source which is arbitrarily far away, and so is specified in terms of its angle from the target. This is the most appropriate way to represent sunlight.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fePointLight&lt;/code&gt;: this represents a point light that emanates from a specific point that is represented as a three-dimensional x/y/z coordinate. This is similar to a light source inside a room or within a scene.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feSpotLight&lt;/code&gt;: this represents a spotlight and which behaves much like a point light, but its beam can be narrowed to a cone, and the light can pivot to other targets.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Each of these three light sources comes with its own attributes that are used to customize the light it generates by specifying the location of the source in the 3D-space. The attributes are outside the scope of this article, but you can learn more about them &lt;a href=&quot;https://www.w3.org/TR/filter-effects-1/#LightSourceDefinitions&quot;&gt;in the specification&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;To create and apply a lighting effect, you need to &lt;strong&gt;nest the light source inside the light type&lt;/strong&gt;. So, you start by choosing the &lt;em&gt;type&lt;/em&gt; of light you want and then picking the &lt;em&gt;source&lt;/em&gt; you want it to emanate from. And then finally you need to specify the &lt;em&gt;color&lt;/em&gt; of your light. The &lt;code&gt;lighting-color&lt;/code&gt; property is used to define the color of the light source for &lt;code&gt;feDiffuseLighting&lt;/code&gt; and &lt;code&gt;feSpecularLighting&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;With the basics of lighting sources covered, we’ll now get to our example.&lt;/p&gt;&lt;p&gt;For the rough paper texture, we’ll be using sun-like light. This means that we will use a white diffuse lighting that emanates from a distant source. Translated to code, our light looks like this:&lt;/p&gt;&lt;pre&gt;&amp;lt;feDiffuseLighting lighting-color=&amp;quot;white&amp;quot; surfaceScale=&amp;quot;2&amp;quot; in=&amp;quot;..&amp;quot; result=&amp;quot;..&amp;quot;&amp;gt;
    &amp;lt;feDistantLight azimuth=&amp;quot;45&amp;quot; elevation=&amp;quot;60&amp;quot; /&amp;gt;
&amp;lt;/feDiffuseLighting&amp;gt;
&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;azimuth&lt;/code&gt; and &lt;code&gt;elevation&lt;/code&gt; attributes determine the position of the source of light in 3D space. There’s &lt;a href=&quot;https://www.photopills.com/articles/understanding-azimuth-and-elevation&quot;&gt;an article by Rafael Pons&lt;/a&gt; that is absolutely fantastic at explaining these two concepts in a simple, easy-to-understand manner, along with beautiful and friendly illustrations to assist with his explanation. I highly recommend checking it out.&lt;/p&gt;&lt;p&gt;Now that we have a light set up, we want to generate our noise that we want to shine this light on. We’ll break the demo down into steps to learn how it’s made.&lt;/p&gt;&lt;p&gt;We gotta start &lt;em&gt;somewhere&lt;/em&gt;, so we’ll start by generating a random, basic noise as a base for our texture:&lt;/p&gt;&lt;pre&gt;&amp;lt;feTurbulence baseFrequency=&amp;#39;0.04&amp;#39; result=&amp;#39;noise&amp;#39; /&amp;gt;
&lt;/pre&gt;&lt;p&gt;Our noise looks like this:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-14.33.33.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 14.33.33&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Next, we’ll shine our light onto it and then take it from there:&lt;/p&gt;&lt;pre&gt;&amp;lt;feTurbulence baseFrequency=&amp;#39;0.04&amp;#39; result=&amp;#39;noise&amp;#39; /&amp;gt;

&lt;strong&gt;&amp;lt;feDiffuseLighting in=&amp;#39;noise&amp;#39; lighting-color=&amp;#39;white&amp;#39; surfaceScale=&amp;#39;2&amp;#39;&amp;gt;
      &amp;lt;feDistantLight azimuth=&amp;#39;45&amp;#39; elevation=&amp;#39;60&amp;#39; /&amp;gt;
&amp;lt;/feDiffuseLighting&amp;gt;&lt;/strong&gt;
&lt;/pre&gt;&lt;p&gt;Shining the light on our noise gives us the following texture:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-14.35.48.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 14.35.48&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;This isn’t the texture result we’re after just yet. The first thing we notice here is the presence of a lot of sharp lines in the texture. We want to get rid of these because a paper surface does not have sharp lines in it. We need to smooth these lines out. We can do that by changing the type of the generated noise to &lt;code&gt;fractalNoise&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&amp;lt;feTurbulence &lt;strong&gt;type=&amp;quot;fractalNoise&amp;quot;&lt;/strong&gt; baseFrequency=&amp;#39;0.04&amp;#39; result=&amp;#39;noise&amp;#39; /&amp;gt;

&amp;lt;feDiffuseLighting in=&amp;#39;noise&amp;#39; lighting-color=&amp;#39;white&amp;#39; surfaceScale=&amp;#39;2&amp;#39;&amp;gt;
      &amp;lt;feDistantLight azimuth=&amp;#39;45&amp;#39; elevation=&amp;#39;60&amp;#39; /&amp;gt;
&amp;lt;/feDiffuseLighting&amp;gt;
&lt;/pre&gt;&lt;p&gt;This removes all those sharp lined edges from our texture:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-14.37.59.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 14.37.59&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;We’re now one step closer to our rough paper texture.&lt;/p&gt;&lt;p&gt;The above texture isn’t &lt;em&gt;rough&lt;/em&gt; enough, though. It lacks the necessary “roughness”. Increasing the amount of tiny detail in it should make it look rougher. To do that, we will increase the value of &lt;code&gt;numOctaves&lt;/code&gt;. We’ll find that around 5 is a great place to get the level of roughness we need:&lt;/p&gt;&lt;pre&gt;&amp;lt;feTurbulence type=&amp;quot;fractalNoise&amp;quot; baseFrequency=&amp;#39;0.04&amp;#39; &lt;strong&gt;numOctaves=&amp;quot;5&amp;quot;&lt;/strong&gt; result=&amp;#39;noise&amp;#39; /&amp;gt;

&amp;lt;feDiffuseLighting in=&amp;#39;noise&amp;#39; lighting-color=&amp;#39;white&amp;#39; surfaceScale=&amp;#39;2&amp;#39;&amp;gt;
      &amp;lt;feDistantLight azimuth=&amp;#39;45&amp;#39; elevation=&amp;#39;60&amp;#39; /&amp;gt;
&amp;lt;/feDiffuseLighting&amp;gt;
&lt;/pre&gt;&lt;p&gt;And our paper texture now looks like this:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-14.40.32.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 14.40.32&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Excellent!&lt;/p&gt;&lt;p&gt;You can play with the live demo here:&lt;/p&gt;&lt;p&gt;
See the Pen Rough Paper Texture with SVG Filters by Sara Soueidan (@SaraSoueidan) on CodePen.&lt;/p&gt;&lt;p&gt;The demo works across all major browsers, including MSEdge.&lt;/p&gt;&lt;p&gt;If you want, you can tweak the effect a little further by playing with the source and distance of the light. For example, decreasing the elevation of the light source from 60 to 40 should increase the contrast between the small hills in the texture. The texture would then look more like this:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/01/Screen-Shot-2019-01-21-at-14.42.28.png?x57826&quot; alt=&quot;Screen Shot 2019-01-21 at 14.42.28&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;I highly recommend playing with the values of the attributes of the light source and the noise and seeing how they impact the resulting texture.&lt;/p&gt;&lt;h2&gt;Final Words&lt;/h2&gt;&lt;p&gt;&lt;code&gt;feTurbulence&lt;/code&gt; is one of SVG’s most interesting and powerful operations. Combined with other primitives and animated, it is capable of generating some really interesting and appealing effects, textures, and interactions.&lt;/p&gt;&lt;p&gt;I strongly believe that &lt;code&gt;feTurbulence&lt;/code&gt; is one of those filters that you’d want to experiment with and break other people’s code down to learn more about it. I still find myself guessing how a texture would look like a lot of times. And since there’s so much we can do with only one texture when used by other primitives, there’s an almost countless set of possible effects that you can make with it. I highly encourage you to check out other people’s work and breaking it down to learn more.&lt;/p&gt;&lt;p&gt;Yoksel has been &lt;a href=&quot;https://codepen.io/yoksel/pens/public/&quot;&gt;experimenting with SVG filters on Codepen&lt;/a&gt; since &lt;a href=&quot;https://www.sarasoueidan.com/blog/svg-filters/&quot;&gt;my SVG Filters talk&lt;/a&gt; came out a few months ago. So you can find quite a bunch of effects to break down and learn from on her Codepen profile.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://codrops-1f606.kxcdn.com/codrops/wp-content/uploads/2019/02/svg-filters-love.gif?x57826&quot; alt=&quot;An animated screenshot of one of Yoksel‘s feTurbulence codepen demos.&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;One of Yoksel’s latest SVG filter experiments leveraging &lt;code&gt;feTurbulence&lt;/code&gt;: SVG Filters are ?&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I hope that this article has inspired you and opened a new door in your imagination to see what you can do with SVG Filters. In the last article in this series, I’ll be sharing some further resources and tools to help you move forward with SVG filters and to start making your own experiments. Stay tuned.&lt;/p&gt;</content:encoded>
</item>
</channel>
</rss>
