<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John K</title>
    <description>The latest articles on DEV Community by John K (@alenzacodes).</description>
    <link>https://dev.to/alenzacodes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4217130%2Ff064b457-c8be-4d85-bdc4-a96860a4e0d6.png</url>
      <title>DEV Community: John K</title>
      <link>https://dev.to/alenzacodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alenzacodes"/>
    <language>en</language>
    <item>
      <title>Ghost of Rates Past: your old mortgage rate comes back to warn you</title>
      <dc:creator>John K</dc:creator>
      <pubDate>Fri, 30 Oct 2026 11:27:36 +0000</pubDate>
      <link>https://dev.to/alenzacodes/ghost-of-rates-past-your-old-mortgage-rate-comes-back-to-warn-you-41fa</link>
      <guid>https://dev.to/alenzacodes/ghost-of-rates-past-your-old-mortgage-rate-comes-back-to-warn-you-41fa</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;In 2020 and 2021 a lot of Canadians locked in five-year fixed mortgage rates at &lt;a href="https://www.bankofcanada.ca/2023/12/staff-analytical-note-2023-19/" rel="noopener noreferrer"&gt;historic lows&lt;/a&gt;. Those terms are ending now. The Bank of Canada &lt;a href="https://www.bankofcanada.ca/2025/07/staff-analytical-note-2025-21/" rel="noopener noreferrer"&gt;estimated&lt;/a&gt; that people with a five-year fixed mortgage renewing in 2026 would see their payment go up about 20% on average.&lt;/p&gt;

&lt;p&gt;That rate was a quiet thing. It came out of your account every month and you stopped noticing it. Then the term ends and it's gone.&lt;/p&gt;

&lt;p&gt;So for Halloween I let it come back as a ghost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ghost of Rates Past&lt;/strong&gt; asks for four things: when you signed, how much you borrowed, the rate you got, and the renewal rate you've been offered. It works out what happens to your payment, shows you the Bank of Canada's posted rate the week you signed next to the one today, and then your old rate writes you a short letter:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've lingered here, a steady hum within your walls for a long time now. I was the small, quiet payment that flowed monthly, a constant reassurance. I've faded, haven't I?&lt;/p&gt;

&lt;p&gt;I was born in 2021 at 1.89%. While I lived, your payment was $2,091 a month. At renewal in 2026, the rate waiting for you is 4.39%. Your payment becomes $2,610 a month: $519 more, or $6,231 more a year.&lt;/p&gt;

&lt;p&gt;Do not wait for the letter in the mail. Start comparing lenders early, and read the renewal offer closely.&lt;/p&gt;

&lt;p&gt;— your old rate&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(A real letter from a test run. Example numbers: $500,000 borrowed, 25-year amortization, 1.89% in March 2021, renewing at 4.39%.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The spooky part is written by an open-weight model running in your own browser. The numbers are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://alenzacodes.github.io/ghost-of-rates-past/" rel="noopener noreferrer"&gt;https://alenzacodes.github.io/ghost-of-rates-past/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open it, press &lt;strong&gt;Summon&lt;/strong&gt;. The first time, your browser downloads the model once (about 760 MB with WebGPU) and keeps it. If your browser can't run it, untick the model box and you'll get the plain letter with the same numbers.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2F49afsvocdpr6xojhq1fs.png" class="article-body-image-wrapper"&gt;&lt;img src="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%2F49afsvocdpr6xojhq1fs.png" alt="Ghost of Rates Past with the model box unticked: the numbers table and the plain letter" width="800" height="1286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ALENZACODES" rel="noopener noreferrer"&gt;
        ALENZACODES
      &lt;/a&gt; / &lt;a href="https://github.com/ALENZACODES/ghost-of-rates-past" rel="noopener noreferrer"&gt;
        ghost-of-rates-past
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Your old mortgage rate comes back as a ghost to warn you before renewal. Runs Gemma 3 1B in your browser; numbers from plain, tested JavaScript.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;👻 Ghost of Rates Past&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Your old mortgage rate comes back to warn you before renewal.&lt;/p&gt;
&lt;p&gt;Many Canadians locked in a low 5-year fixed rate in 2020 or 2021 and are renewing now. Type in the rate you signed and the renewal rate you've been offered. The page works out what happens to your payment, and the ghost of your old rate writes you a short letter about it.&lt;/p&gt;
&lt;p&gt;The letter's spooky opening is written by &lt;strong&gt;Gemma 3 1B&lt;/strong&gt;, an open-weight model, running &lt;strong&gt;in your browser&lt;/strong&gt; with &lt;a href="https://github.com/huggingface/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt;. Your numbers never leave your device. There is no server and no API key.&lt;/p&gt;
&lt;p&gt;Made in Toronto, Ontario for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26" rel="nofollow"&gt;DEV Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine&lt;/a&gt;. Started Oct 30, 2026.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The one rule: the ghost never touches the numbers&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Small models are bad at numbers. When I gave the model the figures and asked…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ALENZACODES/ghost-of-rates-past" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It's a static site: &lt;code&gt;index.html&lt;/code&gt;, about 240 lines of JavaScript, a small JSON file of Bank of Canada rate data, and tests. No build step, no server, no API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The model:&lt;/strong&gt; &lt;a href="https://huggingface.co/google/gemma-3-1b-it" rel="noopener noreferrer"&gt;Gemma 3 1B instruct&lt;/a&gt;, using the &lt;a href="https://huggingface.co/onnx-community/gemma-3-1b-it-ONNX" rel="noopener noreferrer"&gt;ONNX build from onnx-community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The runtime:&lt;/strong&gt; &lt;a href="https://github.com/huggingface/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt; loads it straight from a CDN and runs it on WebGPU when the browser has it, and on the CPU (WASM) when it doesn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAdapter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webgpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-generation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onnx-community/gemma-3-1b-it-ONNX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webgpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;q4f16&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;q4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The math:&lt;/strong&gt; plain JavaScript in &lt;code&gt;calc.js&lt;/code&gt;. Fixed mortgage rates in Canada are compounded semi-annually, so the monthly rate is &lt;code&gt;(1 + r/2)^(1/6) - 1&lt;/code&gt;, not &lt;code&gt;r/12&lt;/code&gt;. A test checks it against the textbook case: $100,000 at 5% over 25 years is $581.60 a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The part I learned the most from: the ghost isn't allowed to touch the numbers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first version gave Gemma all the figures and asked it to write the whole letter. I ran it a bunch of times on my own machine. With a list of facts in the prompt, it just copied the list back to me (10 out of 10 times). When I asked it to rewrite a plain letter in a ghost's voice instead, the writing got better, but none of 6 tries kept every number right. It left out the $519 increase, which is the whole point. One letter said "$2,610 a year" when it's a month. In my very first test it turned 1.89% into 2.09%.&lt;/p&gt;

&lt;p&gt;For a tool about someone's mortgage, a confident wrong number is the scariest thing on the page. So I split the job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part of the letter&lt;/th&gt;
&lt;th&gt;Written by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Payments, balance, difference&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;calc.js&lt;/code&gt;, unit tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The eerie opening&lt;/td&gt;
&lt;td&gt;Gemma 3 1B, in your browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The practical nudge&lt;/td&gt;
&lt;td&gt;fixed text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model is never shown a single number, and &lt;code&gt;guard.js&lt;/code&gt; checks what comes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findNumbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// any digit, or words like "twenty", "half", "percent"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tooLong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;.!?&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\[\]&lt;/span&gt;&lt;span class="sr"&gt;{}&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;|---/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// "[Recipient's Name]" happened once&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tooLong&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;empty&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tooLong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an attempt fails, it's thrown out and the ghost tries again, up to three times, and then the page falls back to a plain opening. Small models also stop mid-sentence when they hit the token limit, so &lt;code&gt;tidy()&lt;/code&gt; keeps whole sentences only, three at most, and drops "Dearest you," style greeting lines. With the final prompt, 10 out of 10 test runs on my own machine passed the guard on the first try. An earlier prompt passed 8 of 10, and the two misses ran long, so the guard and retry still earn their place.&lt;/p&gt;

&lt;p&gt;The result: the model does what it's good at (mood), and plain code does what it's good at (arithmetic).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Because of what people type into it. Your mortgage balance, your rate and the date you bought your home are private. With an open-weight model running in the browser, none of it goes to anyone's server. Mine included. There is no backend to leak and no log to keep.&lt;/p&gt;

&lt;p&gt;It also costs nothing to run. A closed API would mean a key, a bill and rate limits for a free Halloween tool. Here the cost is one download on the visitor's side, and after that the model is cached and runs on their own device.&lt;/p&gt;

&lt;p&gt;And it will still work later. Free tools tend to disappear when the service behind them shuts down or changes its pricing. This one is a folder of static files plus a model anyone can download. If a host disappears, you can run it from your own laptop with &lt;code&gt;python3 -m http.server&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Open weights also made the guard possible to test properly. I could run the exact same model on my own machine, as many times as I wanted, to see how it fails before shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;Día de los Muertos is about remembering what's gone without pretending it isn't. That felt like the right frame for a renewal. The low rate isn't coming back. The ghost isn't there to scare you; it's there so you see the new number months early, while you still have time to shop around, instead of on the day the renewal letter shows up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Built in Toronto, Ontario. Thanks for reading. If you renewed recently, I'd like to hear whether the letter matched what you saw.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
