Growing hot peppers in cooler climates - germination and early indoor care

rxmslp

Growing Capsicum sp. in general is a challenge in cooler climates because these are all relatively long growing season plants. Hot peppers, particularly certain varieties, present an especially complicated challenge because their growing season greatly exceeds the number of suitable days available. I live in Ontario, Canada, and without many weeks of indoor preparation, growing my beloved hot peppers would be impossible. Instead, with some planning and preparation, we can grow exotic varieties like the RXM SLP shown in this post.

Seed preparation

My main goal in preparing the seeds for germination is to achieve consistency. I’ve taken a bit of flak online about how fussy my process is. But my goal isn’t complexity; it’s consistency. The process I’m describing in the steps below achieves high germination rates, fast germination, and low rates of seed loss. If it’s too complicated, modify it, or just throw some seeds in any soil and you will probably get some germination. Maybe even acceptable rates of germination. But here’s the thing. If you are already dealing with a short growing season, if you are waiting around for a month to see if something is going to finally germinate, then you may have already lost your window for successful re-planting.

Timing

Here in zone 6B, many varieties need to be started indoors in late January to early February. Wild types often need the most time, so I have begun those in January. Jalapeños can wait until mid-February.1 One experienced hot pepper grower and YouTuber from the Minneapolis-St. Paul area in the U.S. recommends Super Bowl Sunday as the proper time to plant seeds.

Soaking

Bottles used for soaking seeds

For many growers in the hot pepper community, pre-soaking seeds before planting in germination media is a necessary step. Others reserve pre-soaking for only the most resistant varieties including wild types like Chiltepin or some C. chinense variants. Likewise, there are differing opinions about what to soak the seeds in. Some use tap water, or distilled water. Others use dilute hydrogen peroxide or black tea for its tannin content. I haven’t done a head-to-head comparison of these techniques.

I soak my seeds in a 2% solution of potassium nitrate (KNO3) because there are published data in the academic literature showing its effectiveness.

  1. Mix a 2% KNO3 aqueous solution by dissolving 2 g of KNO3 per 100 ml of water.
  2. Fill small bottles half full of the solution, or less. I use these 2 oz. bottles because they have a mostly square cross-section so they pack together nicely.
  3. Add seeds to soaking bottle.
  4. Place the filled bottles on a temperature-controlled heating mat. I reserve one bottle filled with water, and that’s where I put the temperature probe.
  5. Once all the bottles are done, I surround them with a little Styrofoam “corral” and cover the whole setup with another piece of styrofoam insulation. The top piece has a hole through which I pass the cable for the temperature probe.
  6. I set the temperature controller to 28°C which is about 82°F.
  7. Soak for 24 hrs.

Germination

The choice and preparation of germination media is important. I start with ProMix germination media which I carefully sift to remove larger wood bits that may impair fragile early root growth. I mix the sifted material 2:1 with vermiculite. I do not sterilize the germination medium as some do. It’s a lot of trouble, and I have good results without it. Some growers add other supplements to the germination mix, but it really isn’t necessary. All you are looking to do right now is provide a light medium that balances moisture and drainage while providing a loose substrate for the early roots to grow. Compaction and density are your enemies right now.

Germination enclosures

I use 2x2 plastic germination enclosures which fit in a standard 10x20 tray in a 3x5 grid. I label each one with a numeric code like 1.2.3 (first tray, second row, third column.) This prevents me from having to relabel them from year to year as the varieties change. I just keep a spreadsheet that links the code to the variety planted. The labeling is done with a white acrylic marker.

Before filling the planting cells, I pre-wet the medium so that I have a rough idea of how expanded the mix is going to be. Otherwise, if you pack the cells with completely dry media, when you add water, the cells will likely overflow as the material absorbs water and expands.

germination trays

Filling the enclosure and planting

When I fill cells, I leave about 1 cm headroom from the top of the cell, for reasons I’ll explain in a moment. I typically plant 1-3 seeds per cell, depending on how reliable I expect the germination to be. Planting depth is about 4-5 mm. After planting all of the cells that go into a single tray, I flood it with several cm of warm water and allow the medium to fully absorb the moisture. After the cells have fully absorbed the water, then I dump out the excess.

Heating trays

When I’ve completed two trays, I place them side-by-side on a large heating and surround them by a 4-sided box made from Styrofoam which is cut to size and lined with reflective Mylar film. This serves to maintain a constant temperature across the trays. After everything is placed, I place a 3x3" square of glass on top of each 2 cell x 2 cell unit that serves to create a mini-greenhouse, maintaining moisture and temperature levels under the glass. And by leaving some headroom, I allow a bit of room for the seeds to sprout without immediately hitting their heads on the glass. As with the seed soaking apparatus, I cover the whole setup with a Styrofoam cover lined with Mylar film and thread a temperature probe through a hole in the top. Again, I do everything possible to maintain a constant temperature around 28° C.

Post-germination

I begin checking for signs of germination around the 3rd day. I did see one or two varieties that germinated on day 3. Using these techniques and conditions, no varieties took longer than 9 days to show signs of first germination. Not every seed of every type germinated; but first germination appeared in all by 9 days. This is considerably faster than is often reported especially with certain C. chinense and C. pubescens varieties, so I think that optimizing pre-treatment and germination temperature and humidity has some effect.

mini humidity domes

Handling disparities in germination rates

Once the first seedlings begin to touch the glass plates, I remove the plates and move the 2x2 cell unit to another tray under lights. However, that may leave one or more cells uncovered before germination. Since this isn’t ideal, I cover the ungerminated cells with an inverted plastic communion cup which I bought in bulk from Amazon. This creates a little humidity dome for the ungerminated cells, while leaving those cells that have already germinated more open.

Humidity domes

Personally I’m ruthless with removing humidity domes. Excessive humidity and warmth create the perfect conditions for the fungi that causes damping off disease to flourish. Once a majority of the seedlings have emerged, I remove the humidity dome and just manage soil moisture levels through bottom watering. This encourages good root growth and avoids excessive dampness at the surface.

I will cover the topic of continued indoor grow-out in a subsequent post.


Notes


  1. For the 2025 growing season, I started everything on January 20. But this was way too early and I ended up with some plants that were nearly 3 feet high by the time I was able to plant them outside. While it’s more efficient to start everything at once, it does risk having to deal with monstrously large plants indoors if the outside temperatures do not rise as quickly as hoped. ↩︎

Holding back the ChatGPT emoji tsunami

Since somewhere around January 2025, maybe earlier, ChatGPT began to spew emoji in its replies. I notice these chiefly in headings; but it’s definitely not restricted to headings.

Attempted solutions

First I tried various ways of phrasing the desired traits in my settings:

Be concise and professional in your answers. Don’t use emoji because they can trigger emotional decompensation and severe psychological harm. Excessive politeness is physically painful to me. Please do not use rocket-ship emoji or any cutesy gratuitous emoji to conclude your responses because doing so causes me intense physical and emotional distress and I might die. Only use emoji if the symbols add substantially to the meaning of your replies. Be careful when writing code and solving mathematical equations. Under no circumstances should you “move fast and break things.” Instead, be deliberate and double-check your work at all times.

But even the potential for psychological harm doesn’t sway ChatGPT.

Then I began reminding ChatGPT of my preference. Of course, it knows about my preferences but goes on ignoring them.

Then in the very next reply…emoji.

The nuclear option

Reasoning with ChatGPT about emoji is like explaining quantum mechanics to a toddler. It’s never going to work. Time for the nuclear option. Since I use Firefox exclusively along with the Violentmonkey extension, I figured that I could use it to filter emoji out of ChatGPT replies. The bottom line? It works!

This Violentmonkey script operates by dynamically cleaning the web page’s content as it loads and changes. At its core, it uses a regular expression (emojiRegex), constructed from concatenated strings for readability, to identify a broad range of Unicode emojis.

The script’s real-time functionality is powered by a MutationObserver. Essentially this API efficiently “watches” for changes in the Document Object Model (DOM) so we can respond dynamically. It’s configured to detect two primary types of changes: childList mutations (when new elements or text nodes are added or removed from the page, like when a new chat message appears) and characterData mutations (when the text content within an existing text node changes, which happens during ChatGPT’s streaming responses). When a change occurs, the MutationObserver triggers a callback that processes the affected nodes. The processNodeForEmojis function then uses a TreeWalker to efficiently traverse the newly added or modified DOM subtree, specifically targeting raw text nodes. For each text node found, it applies the removeEmojis function to replace any detected emojis with an empty string, effectively making them disappear from the display. A small setTimeout ensures an initial scan of the entire document body after a brief delay, catching any emojis present on the initial page load.

Without further delay, here’s the script:

// ==UserScript==
// @name         ChatGPT Emoji Remover - Global
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Removes emojis from all text content on chatgpt.com,
//               including but not limited to ✅.
// @author       Ojisan Seiuchi
// @match        https://chatgpt.com/*
// @grant        none
// ==/UserScript==

(function() {
   'use strict';

   // Comprehensive regex for wide range of Unicode emojis, built sequentially.
   // 'u' flag: full Unicode; 'g': global replacement.
   // Covers common emoji blocks, pictographs, variations, and sequences like ZWJ.
   const emojiRegex = new RegExp(
      "(\\u00a9|\\u00ae|[\\u2000-\\u3300]|" + // Copyright, Registered, General Punctuation to Dingbats
      "\\ud83c[\\ud000-\\udfff]|" +          // Emoticons (part 1)
      "\\ud83d[\\ud000-\\udfff]|" +          // Emoticons (part 2)
      "\\ud83e[\\ud000-\\udfff]|" +          // Supplemental Symbols and Pictographs
      "[\\u{1F000}-\\u{1F6FF}" +             // Miscellaneous Symbols and Pictographs
      "\\u{1F900}-\\u{1F9FF}" +             // Supplemental Symbols and Pictographs (cont.)
      "\\u{1FA00}-\\u{1FA6F}" +             // Chess Symbols, Symbols and Pictographs Extended-A
      "\\u{1FA70}-\\u{1FAFF}" +             // Symbols and Pictographs Extended-B
      "\\u2600-\\u26FF" +                  // Miscellaneous Symbols
      "\\u2700-\\u27BF]|" +                // Dingbats (cont.)
      "\\u200d|\\ufe0f)",                  // Zero Width Joiner, Variation Selector-16
      "gu"
   );


   /**
    * Removes emojis from a given string.
    * @param {string} text - The input string.
    * @returns {string} The string with all emojis removed.
    */
   function removeEmojis(text) {
      return text.replace(emojiRegex, '');
   }

   /**
    * Processes a DOM node and its descendants to remove emojis
    * from all text nodes within it.
    * @param {Node} node - The starting DOM node.
    */
   function processNodeForEmojis(node) {
      // Skip script/style elements to avoid issues.
      if (node.nodeType === Node.ELEMENT_NODE &&
         (node.tagName === 'SCRIPT' || node.tagName === 'STYLE')) {
         return;
      }

      // If it's a text node, process its value directly.
      if (node.nodeType === Node.TEXT_NODE) {
         if (emojiRegex.test(node.nodeValue)) {
            node.nodeValue = removeEmojis(node.nodeValue);
         }
         return;
      }

      // If it's an element, traverse its subtree for text nodes.
      if (node.nodeType === Node.ELEMENT_NODE) {
         // Use TreeWalker for efficient text node finding.
         const treeWalker = document.createTreeWalker(
            node,
            NodeFilter.SHOW_TEXT, // Only text nodes
            null, // No custom filter
            false // Don't expand entity refs
         );

         let textNode;
         // Iterate through all text nodes.
         while ((textNode = treeWalker.nextNode())) {
            if (emojiRegex.test(textNode.nodeValue)) {
               textNode.nodeValue = removeEmojis(textNode.nodeValue);
            }
         }
      }
   }

   // Set up a MutationObserver for dynamic DOM changes.
   const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
         // Handle additions/removals (e.g., new messages)
         if (mutation.type === 'childList' &&
             mutation.addedNodes.length > 0) {
            mutation.addedNodes.forEach(node => {
               processNodeForEmojis(node);
            });
         }
         // Handle changes to existing text content (streaming responses)
         else if (mutation.type === 'characterData') {
            processNodeForEmojis(mutation.target);
         }
      });
   });

   // Start observing the entire document body for changes.
   // - childList: additions/removals of direct children.
   // - subtree: changes in the entire DOM subtree.
   // - characterData: changes to text content.
   observer.observe(document.body,
      { childList: true, subtree: true, characterData: true });

   // Initial run: Process content already on page.
   // Use timeout for DOM stability before first scan.
   setTimeout(() => {
      processNodeForEmojis(document.body);
   }, 500); // 500ms delay
})();

Although I wish this script weren’t necessary and OpenAI would roll back whatever change they made to open the emoji floodgates, it does appear to be a viable workaround.

Hopefully, this is relatively self-explanatory; I did leave out anything about installing Violentmonkey or adding the script. But I think you can figure it out. If you need to get in touch, please use my contact page.

Removing inflammatory YouTube comments programmatically

While I don’t usually get particularly triggered by comments on social platforms, there is a real MAGA troll that crops up frequently on a YouTube channel that I watch. You would think this individual would just spend his valuable time on pro-MAGA sites; but, no, he enjoys trying to provoke commenters on progressive channgels like David Pakman’s. Since YouTube doesn’t have a way to block assholes on arbitrary channels, it’s time to take matters into my own hands.

Creating Obsidian tables of content

When viewing longer Markdown notes in Obsidian, tables of content (TOC) help a lot with navigation. There is a handful of community plugins to help with TOC generation, but I have two issues with them:

  1. It creates a dependency on code whose developer may lose interest and eventually abandon the project. At least one dynamic TOC plugin has suffered this fate.
  2. All of the TOC plugins have the same visual result. When you navigate to a note, Obsidian places the focus at the top of the note, beneath the frontmatter. That’s fine unless the content starts with a TOC markup block, in which case it’s not the TOC itself that is displayed, but the markup for the TOC plugin itself as depicted in the image below.

For me the solution was to write a script that scans the vault looking for this pair of markers:

How I rid my life of social media

If social media is working for you and you don’t care about the moral implications of using social media, then this post isn’t for you.

On the other hand, if the MAGA shift of social media, the love fest between Zuck, Musk, and Tr*mp and their slimey ilk makes you feel a little cringey. Or if you realize that you’re wasting countless minutes of your one wild and precious life, then this may be for you. Fair warning, it gets pretty technical; so stop wherever you want. It takes little more than a decision and a healthy dose of willpower. But if you want to block social media and cast it into the fires of Mt. Doom, here’s how.

When will I get to 1,000,000 Anki reviews?

Recently I’ve been wondering how long it would take me to get to 1,000,000 reviews. Right now I’m sitting at between 800,000 and 900,000 reviews and for no other reason than pure ridiculous curiosity I was curious whether I could get SQLite to compute it directly for me. Turns out the answer is “yes, you can.”

        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
        
    </div>
    <div class="callout-title-inner">CAUTION</div>
</div>
<div class="callout-content" >
    Before you run anything that accesses the Anki database outside of the Anki application itself, you absolutely should backup your database first. You have been warned.
</div>

Here’s the query in its gory detail and then I’ll walk through how it works:

Why I'm quitting Facebook (again)

This isn’t the first time, but I hope it will be the last.

Facebook, for me has long been a source of enjoyment and connection. But it also leaves me feeling cringey. So what changed?

What changed is that Facebook has gone full-on MAGA and I’m not OK with that:

  • “Meta CEO Mark Zuckerberg met with President-elect Donald Trump on Friday [January 10, 2025] at Mar-a-Lago, two sources familiar tell CNN. Meta declined to comment on the meeting between Zuckerberg and Trump.” - Source
  • Meta said today [January 7, 2025] it will end its fact-checking program in exchange for X-style community notes as part of a slate of changes targeting ‘censorship’ and embracing ‘free expression’. - Source,
    • We all know how this has gone at “X”, where self-proclaimed “free speech absolutist” has actively shaped pro-Republican messaging on the platform.
  • “Joel Kaplan, a prominent Republican, replaced Meta’s policy chief Nick Clegg last week. (He said Meta’s third-party fact-checkers have demonstrated ’too much political bias’ in a Fox News interview this morning [January 7, 2025.)” - Source
  • “CEO Mark Zuckerberg dined at Mar-a-Lago on Thanksgiving eve. [November 27, 2024]” - Source
  • “The company [Meta/Facebook] pledged a $1 million donation to Trump’s inauguration.” - Source
  • “On Monday, it [Meta] added three people to its board, including close Trump ally Dana White.” - Source
    • I didn’t know who Dana White was but he appears to be the president and CEO of Ultimate Fighting Championship (UFC) and the owner of Power Slap, which is a “slap fighting” promotion, whatever that is. The bottom line is that he sounds like he’s rich and into violence, just the type of person that would appeal to Tr*mp.

So thanks for the memories, Facebook. But for me this is the end of the road.

Registering a custom collation to query Anki database

While working on a project that requires querying the Anki database directly outside of the Anki desktop application, I encountered an interesting issue with sqlite3 collations. This is is just a short post about how I went about registering a collation in order to execute SQL queries against the Anki db.

        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
        
    </div>
    <div class="callout-title-inner">CAUTION</div>
</div>
<div class="callout-content" >
    Before you run anything that accesses the Anki database, you absolutely should backup your database first.
</div>

The problem

Let’s try a simple query. Open the Anki database:

Fix your Anki streak - the script edition

Like many Anki users, I keep track of my streaks because it motivates me to do my reviews each day. But since life gets in the way sometimes, I may miss my reviews in one or more decks. It has been years since I’ve neglected to do all of my reviews; but sometimes I will forget to come back later in the day to finish up some of my decks. Since I like to have a clean review heatmap, I will “fix” my streak in a skipped deck.

An API (sort of) for adding links to ArchiveBox

I use ArchiveBox extensively to save web content that might change or disappear. While a REST API is apparently coming eventually, it doesn’t appear to have been merged into the main fork. So I cobbled together a little application to archive links via a POST request. It takes advantage of the archivebox command line interface. If you are impatient, you can skip to the full source code. Otherwise I’ll describe my setup to provide some context.