<?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: fralsare</title>
    <description>The latest articles on DEV Community by fralsare (@fralsare).</description>
    <link>https://dev.to/fralsare</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%2F4128492%2F8593977f-b6b5-49c3-9d3e-cb8d4a521613.jpg</url>
      <title>DEV Community: fralsare</title>
      <link>https://dev.to/fralsare</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fralsare"/>
    <language>en</language>
    <item>
      <title>Building Fralculator: an offline-first, step-by-step calculator in Electron</title>
      <dc:creator>fralsare</dc:creator>
      <pubDate>Thu, 01 Oct 2026 02:11:01 +0000</pubDate>
      <link>https://dev.to/fralsare/building-fralculator-an-offline-first-step-by-step-calculator-in-electron-8ph</link>
      <guid>https://dev.to/fralsare/building-fralculator-an-offline-first-step-by-step-calculator-in-electron-8ph</guid>
      <description>&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;&lt;br&gt;
Most calculators evaluate. I wanted one that explains the evaluation:&lt;br&gt;
given 3 × (4 + 5), show the ordered PEMDAS trace — 4 + 5 = 9, then&lt;br&gt;
3 × 9 = 27 — rendered as real math notation, with a plain-English&lt;br&gt;
sentence underneath.&lt;br&gt;
Github - &lt;a href="//github.com/fralsare/fralculator"&gt;github.com/fralsare/fralculator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Constraints I set for myself:&lt;/p&gt;

&lt;p&gt;Offline by default. The only allowed network calls: an optional currency rate fetch and a one-time ~40 MB model download for voice.&lt;br&gt;
No LLM required. Explanations are rule-based today; the LLM is a&lt;br&gt;
pluggable backend that doesn't exist yet.&lt;br&gt;
Electron + React + Vite + TypeScript, MIT licensed, packaged for&lt;br&gt;
Windows and Linux.&lt;br&gt;
It's called Fralculator. Source: github.com/fralsare/fralculator.&lt;br&gt;
Here's how the interesting bits work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The math engine: tokenizer → parser → evaluator with a trace&lt;/strong&gt;&lt;br&gt;
The engine is deliberately classic. A tokenizer turns the string into&lt;br&gt;
tokens (numbers, operators, parens, unary minus), a recursive-descent&lt;br&gt;
parser builds an AST with the usual precedence climbing, and the&lt;br&gt;
evaluator walks it. The twist: the evaluator also records a step&lt;br&gt;
trace as it reduces sub-expressions.&lt;/p&gt;

&lt;p&gt;// Pseudocode of the reduction loop&lt;br&gt;
function eval(node: AstNode, trace: Step[]): number {&lt;br&gt;
  switch (node.kind) {&lt;br&gt;
    case "binary": {&lt;br&gt;
      const left = eval(node.left, trace);&lt;br&gt;
      const right = eval(node.right, trace);&lt;br&gt;
      if (node.precedence &amp;lt; parentPrecedence) {&lt;br&gt;
        // Only record steps that are "outer" — inner ones already recorded&lt;br&gt;
        trace.push({&lt;br&gt;
          expr: &lt;code&gt;${fmt(left)} ${opSymbol(node.op)} ${fmt(right)}&lt;/code&gt;,&lt;br&gt;
          result: left op node.op right,&lt;br&gt;
        });&lt;br&gt;
      }&lt;br&gt;
      return apply(node.op, left, right);&lt;br&gt;
    }&lt;br&gt;
    case "unary":&lt;br&gt;
      return eval(node.operand, trace);&lt;br&gt;
    case "literal":&lt;br&gt;
      return node.value;&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Two design details that matter:&lt;/p&gt;

&lt;p&gt;Steps are recorded at the precedence boundary. We don't log every&lt;br&gt;
single reduction — we log each operation as the user would write it&lt;br&gt;
(parenthesized sub-expressions first, in PEMDAS order). This is the&lt;br&gt;
difference between "a log of the evaluator" and "a worked solution".&lt;br&gt;
The trace is data, not DOM. The UI renders it with KaTeX in a&lt;br&gt;
numbered list; the plain-English explainer and the "explain as image"&lt;br&gt;
feature both consume the same trace structure. One source of truth,&lt;br&gt;
three consumers.&lt;br&gt;
Unary minus is the classic gotcha: -3^2 parses as -(3^2), and the&lt;br&gt;
trace must say so, not silently agree with whatever the user meant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Autocorrect: the "6 pls 2" story&lt;/strong&gt;&lt;br&gt;
Natural-language input in a calculator is either a tiny rule engine or&lt;br&gt;
[Insert: fralculatorScreen_1.png — the graph tab, f(x) with 600 samples]&lt;/p&gt;

&lt;p&gt;Natural-language input in a calculator is either a tiny rule engine or&lt;br&gt;
an LLM. Offline means rule engine. And rule engines get weird input.&lt;/p&gt;

&lt;p&gt;Testing the voice path, the Whisper transcript of someone saying "six&lt;br&gt;
plus two" came back as 6 pls 2 — the model heard a word it knew.&lt;br&gt;
Rather than special-case transcripts, I added word-shorthand rules&lt;br&gt;
to the typed pipeline too:&lt;/p&gt;

&lt;p&gt;// Word shorthands — applied only as whole words, case-insensitive.&lt;br&gt;
// They run BEFORE numeric normalization so "6 pls 2" → "6 plus 2".&lt;br&gt;
const WORD_SHORTHANDS: ReadonlyArray&amp;lt;[RegExp, string]&amp;gt; = [&lt;br&gt;
  [/\bplus\b/i, "+"],&lt;br&gt;
  [/\bpls\b/i, "+"],&lt;br&gt;
  [/\bminus\b/i, "-"],&lt;br&gt;
  [/\bmin\b/i, "-"],&lt;br&gt;
  [/\btimes\b/i, "×"],&lt;br&gt;
  [/\bmultiplied\b(?:\s+by)?/i, "×"],&lt;br&gt;
  [/\bdivided\b\s+by\b/i, "÷"],&lt;br&gt;
  [/\bto\b\s+the\b\s+power\s+of\b/i, " ^ "],&lt;br&gt;
  [/\bpercent\b/i, "%"],&lt;br&gt;
];&lt;br&gt;
The rules run in a specific order: shorthands first, then numeric&lt;br&gt;
normalization (5x3 → 5×3, ** → ^, . at end of string), then&lt;br&gt;
a single pass of balanced-parenthesis repair. Every edit is&lt;br&gt;
accumulated into a human-readable description ("6 pls 2" → "6 plus 2" → 6 + 2) that the UI shows under the display — because in a math tool,&lt;br&gt;
silently changing the user's input is a bug, not a feature.&lt;/p&gt;

&lt;p&gt;The same aliases back the natural-language regexes (what is, solve,&lt;br&gt;
compute, "x squared", "square root of"), so typed, voice, and&lt;br&gt;
autocorrected input all converge on one grammar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Offline voice: Whisper-tiny WASM in an Electron worker&lt;/strong&gt;&lt;br&gt;
Voice input uses the transformers.js WASM build of Whisper-tiny. The&lt;br&gt;
layout in Electron:&lt;/p&gt;

&lt;p&gt;The main renderer calls getUserMedia, records chunks, and hands a&lt;br&gt;
Float32Array (resampled to 16 kHz) to a dedicated worker thread&lt;br&gt;
— decoding a WASM model on the main thread would stutter the UI.&lt;br&gt;
First run downloads the ~40 MB model; subsequent runs read it from an&lt;br&gt;
IndexedDB cache. This is the app's only big download, and it's&lt;br&gt;
announced in the UI before it happens.&lt;br&gt;
The transcript goes through the same autocorrect pipeline as&lt;br&gt;
typed input. Voice never gets special treatment in the math engine —&lt;br&gt;
it's just text with more typos.&lt;br&gt;
The payoff: after first launch, audio bytes leave the machine zero.&lt;br&gt;
Every voice result auto-saves to history tagged with a mic icon,&lt;br&gt;
searchable alongside typed entries.&lt;/p&gt;

&lt;p&gt;[Insert: fralculatorScreen_2.png — history with voice-tagged entries]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Why a calculator ships a subnet calculator&lt;/strong&gt;&lt;br&gt;
The most surprising tab is EHCalc, a small security toolkit built&lt;br&gt;
because I'm studying cybersecurity and kept wanting reference tools at&lt;br&gt;
arm's length:&lt;/p&gt;

&lt;p&gt;Subnet calculator (CIDR/mask → network, broadcast, wildcard, usable&lt;br&gt;
hosts, range, class)&lt;br&gt;
MD5 / SHA-1 / SHA-256 / SHA-512 via WebCrypto&lt;br&gt;
10 encoders: Base64 ⇄ Hex ⇄ URL, ROT13, Atbash, ASCII ⇄ (with a&lt;br&gt;
direction-aware error message when you paste text into a decode box)&lt;br&gt;
Password strength (entropy, crack-time tiers) + a CSPRNG generator&lt;br&gt;
using crypto.getRandomValues() with rejection sampling per character&lt;br&gt;
class — and it never writes anything to storage, by design&lt;br&gt;
Port lookup with risk notes (Telnet's clear text; SMB's… history)&lt;br&gt;
Architecturally it's the same lesson as the math engine: pure,&lt;br&gt;
testable functions with zero DOM imports, wrapped in small presentational&lt;br&gt;
components. The unit tests for the encoders and subnet math are the&lt;br&gt;
part I'd show an interviewer.&lt;/p&gt;

&lt;p&gt;[Insert: fralculatorScreen_main2.png — the EHCalc subnet calculator]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Packaging: five installers, no backend&lt;/strong&gt;&lt;br&gt;
electron-builder produces NSIS + portable .exe on Windows and&lt;br&gt;
AppImage + deb + rpm on Linux from the same workflow. CI runs&lt;br&gt;
npm ci → tsc → vitest → vite build, then the release workflow builds&lt;br&gt;
on both ubuntu-latest and windows-latest and uploads via&lt;br&gt;
softprops/action-gh-release.&lt;/p&gt;

&lt;p&gt;Honest caveat: the Windows binaries are currently unsigned (a code&lt;br&gt;
signing cert is a real cost for a solo student project) — Windows&lt;br&gt;
SmartScreen will warn. On Linux, AppImage is the recommended start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's next&lt;/strong&gt;&lt;br&gt;
The roadmap's headline item is the backend the code already reserves a&lt;br&gt;
slot for: a local LLM (e.g. a small GGUF model via WASM/ONNX) for&lt;br&gt;
the explanation and natural-language paths — still offline, still no&lt;br&gt;
account. Until then, the rule engine is what you get, and it's decent.&lt;/p&gt;

&lt;p&gt;Fralculator — MIT licensed, offline-first, Windows + Linux:&lt;br&gt;
github.com/fralsare/fralculator&lt;/p&gt;

&lt;p&gt;If you build offline-first Electron apps, the two things I'd tell you:&lt;br&gt;
put the heavy model work in a worker, and make autocorrect tell the&lt;br&gt;
user what it did.&lt;/p&gt;

&lt;p&gt;Built this solo between classes. Issues and PRs are welcome; if you&lt;br&gt;
use it for study, there's a donation link in the README that funds my cybersecurity coursework.&lt;/p&gt;

</description>
      <category>electron</category>
      <category>opensource</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How I built a news reader that wants nothing from you: five topics, one window</title>
      <dc:creator>fralsare</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:50:01 +0000</pubDate>
      <link>https://dev.to/fralsare/how-i-built-a-news-reader-that-wants-nothing-from-you-five-topics-one-window-1blp</link>
      <guid>https://dev.to/fralsare/how-i-built-a-news-reader-that-wants-nothing-from-you-five-topics-one-window-1blp</guid>
      <description>&lt;h1&gt;
  
  
  I built a news reader that wants nothing from you : five topics, one window
&lt;/h1&gt;

&lt;p&gt;Every news app I tried in the last few years asks for the same three&lt;br&gt;
things before you see a single headline: an account, an API key, and&lt;br&gt;
eventually a subscription. I got tired of negotiating with software&lt;br&gt;
about reading the news, so I built &lt;a href="https://github.com/fralsare/fralsare-daily" rel="noopener noreferrer"&gt;fralsare-daily Github Link&lt;/a&gt; - a&lt;br&gt;
small, open-source desktop app that reads public RSS feeds and stops&lt;br&gt;
asking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;fralsare-daily is a Tauri 2 desktop app (Windows + Linux) that aggregates&lt;br&gt;
public RSS feeds into &lt;strong&gt;five fixed topics&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Geopolitics &amp;amp; Conflict&lt;/li&gt;
&lt;li&gt;Politics &amp;amp; Governance&lt;/li&gt;
&lt;li&gt;Sports&lt;/li&gt;
&lt;li&gt;Technology &amp;amp; Business&lt;/li&gt;
&lt;li&gt;Viral &amp;amp; Social Issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five, not fifty. That's the point: a fixed, small set of shelves you can&lt;br&gt;
clear in ten minutes, instead of a personalized infinite scroll.&lt;/p&gt;

&lt;p&gt;Clicking a story opens it in a &lt;strong&gt;built-in reader&lt;/strong&gt;. The whole experience&lt;br&gt;
stays in one window — article body, images, internal links with a back&lt;br&gt;
button — with an "Open in browser" escape hatch one click away. There's&lt;br&gt;
also an images / text-only toggle for slow connections.&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%2Fuvds2ujj50pvbnr85pf4.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%2Fuvds2ujj50pvbnr85pf4.png" alt="Screenshot" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Tauri 2?
&lt;/h2&gt;

&lt;p&gt;The constraint that shaped the architecture: this is a &lt;em&gt;news reader&lt;/em&gt;, not a&lt;br&gt;
browser. It needs to fetch feeds and article HTML from arbitrary domains —&lt;br&gt;
which a plain web frontend can't do (CORS) — but it should weigh what a&lt;br&gt;
desktop utility weighs, not 100+ MB of Chromium.&lt;/p&gt;

&lt;p&gt;So the split is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust backend&lt;/strong&gt; owns all network I/O: it fetches feeds, parses RSS 2.0
and Atom with a streaming parser (no full DOM build), fetches article
pages, and does the first sanitization pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla TypeScript + Vite frontend&lt;/strong&gt; renders the UI. No framework —
the app is one sidebar, one list, one reader view, and a state machine
of about 200 lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: a ~15–20 MB release binary with CI-built packages for both&lt;br&gt;
platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that took the most iteration: sanitization
&lt;/h2&gt;

&lt;p&gt;Rendering third-party HTML inside your app is the security-critical part.&lt;br&gt;
I ended up with defense in depth:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extraction&lt;/strong&gt; — before anything else, the backend picks the actual
article body (largest &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; block, then &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, else the
document) so site chrome, nav bars, and "related stories" never enter
the reader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust regex pass&lt;/strong&gt; — strips script/style/iframe/form and other
executable tags, removes &lt;code&gt;on*&lt;/code&gt; attributes and inline styles, and
neutralizes &lt;code&gt;javascript:&lt;/code&gt; URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend DOM pass&lt;/strong&gt; — a second sanitizer walks the parsed DOM and
removes the same tag/attribute classes, because regex alone is not a
parser and I don't trust one layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation guard&lt;/strong&gt; — the Tauri webview blocks any navigation away
from the app shell; a link inside an article either pushes the in-app
history stack or, if it can't be rendered safely, falls back to the
system browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Images only render after the backend checks the response's magic bytes&lt;br&gt;
(actual JPEG/PNG/GIF/WEBP), which avoids the classic "HTML file with an&lt;br&gt;
image content type" trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A real HTML parser (html5ever) in the backend would replace the regex
layer — I traded robustness for a lighter dependency tree, but that's a
defensible trade I might revisit.&lt;/li&gt;
&lt;li&gt;Feed selection is currently fixed per topic. Custom feeds are the
obvious next feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's MIT-licensed and the source is small enough to read in an afternoon : &lt;a href="https://github.com/fralsare/fralsare-daily" rel="noopener noreferrer"&gt;Repo URL&lt;/a&gt;&lt;br&gt;
Prebuilt Windows and Linux binaries are on the releases page,&lt;br&gt;
or &lt;code&gt;cargo tauri dev&lt;/code&gt; if you'd rather build it.&lt;/p&gt;

&lt;p&gt;If you've been looking for a reason to move off the infinite scroll, this&lt;br&gt;
is my attempt at the reason: fewer topics, no account, no keys, no&lt;br&gt;
tracking, one window.&lt;/p&gt;

</description>
      <category>news</category>
      <category>rust</category>
      <category>tauri</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Built a Linux CLI That Auto-Pauses Your "Active Time"</title>
      <dc:creator>fralsare</dc:creator>
      <pubDate>Thu, 24 Sep 2026 12:32:03 +0000</pubDate>
      <link>https://dev.to/fralsare/how-i-built-a-linux-cli-that-auto-pauses-your-active-time-3917</link>
      <guid>https://dev.to/fralsare/how-i-built-a-linux-cli-that-auto-pauses-your-active-time-3917</guid>
      <description>&lt;p&gt;A few weeks ago I wanted something simple: a way to see how much of my day I was actually using my computer versus just sitting at it. My existing time trackers counted every idle minute as wasted, which felt wrong - I wasn't being lazy, I was just… not doing anything.&lt;/p&gt;

&lt;p&gt;So I built timerCLI, a tiny command-line tool that tracks your active time and automatically pauses it whenever you go idle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;The trick is simple but clever: Linux exposes raw input device events at /dev/input/event*. These are the kernel-level files that capture every keyboard press, mouse movement, and touch event.&lt;/p&gt;

&lt;p&gt;All timerCLI does is:&lt;/p&gt;

&lt;p&gt;Open every readable /dev/input/event* device (non-blocking)&lt;br&gt;
Continuously drain any pending input events&lt;br&gt;
Track the timestamp of the last event&lt;br&gt;
If no input arrives for IDLE_LIMIT seconds (default 30s), pause the timer&lt;br&gt;
Resume the instant input returns&lt;br&gt;
That's the entire engine — no machine-learning, no tracking servers, no telemetry. Just raw input watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the output looks like
&lt;/h2&gt;

&lt;p&gt;Idle:    0s | Active: 00:04&lt;br&gt;
⏸ Idle 31s — pausing…&lt;br&gt;
▶  Resumed.&lt;br&gt;
Idle:    0s | Active: 00:41   ← keeps counting down while paused&lt;/p&gt;

&lt;p&gt;When you stop typing/moving for 30 seconds, it pauses and shows a ⏸️ indicator. The moment you use your computer, it's back to counting active time. Stop it anytime with Ctrl+C and it prints your final active/paused totals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keep it tiny?
&lt;/h2&gt;

&lt;p&gt;I deliberately wrote this using only Python's standard library - no psutil, no pyautogui, no dependencies to install. That means:&lt;/p&gt;

&lt;p&gt;It runs on almost any Linux box with Python 3&lt;br&gt;
Nothing to pip install&lt;br&gt;
Easy to read and modify&lt;br&gt;
The only real requirement is permission to read /dev/input/event*, which needs your user in the input group:&lt;/p&gt;

&lt;p&gt;sudo usermod -aG input $USER   # then log out and back in&lt;/p&gt;

&lt;h2&gt;
  
  
  It's open source
&lt;/h2&gt;

&lt;p&gt;timerCLI is MIT licensed and fully open source. You can read the whole thing in a few hundred lines here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/fralsare/timerCLI" rel="noopener noreferrer"&gt;timerCLI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built it as a learning project, but I'd love for people to actually use it. I'm also accepting donations (GitHub Sponsors + UPI) to fund my cybersecurity studies - every bit helps. 🙏&lt;/p&gt;

</description>
      <category>cli</category>
      <category>linux</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
