<?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: Karim Masmoudi</title>
    <description>The latest articles on DEV Community by Karim Masmoudi (@kernelhunter).</description>
    <link>https://dev.to/kernelhunter</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%2F4131947%2F330e7c02-5ab1-41f5-99ea-506d6014a9ad.jpg</url>
      <title>DEV Community: Karim Masmoudi</title>
      <link>https://dev.to/kernelhunter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kernelhunter"/>
    <language>en</language>
    <item>
      <title>I built my portfolio as an operating system, with no framework</title>
      <dc:creator>Karim Masmoudi</dc:creator>
      <pubDate>Fri, 18 Sep 2026 18:09:56 +0000</pubDate>
      <link>https://dev.to/kernelhunter/i-built-my-portfolio-as-an-operating-system-with-no-framework-4e98</link>
      <guid>https://dev.to/kernelhunter/i-built-my-portfolio-as-an-operating-system-with-no-framework-4e98</guid>
      <description>&lt;p&gt;A CV is a list of claims. An operating system is a thing you can use. That's the whole premise behind &lt;a href="https://karimmasmoudi.vercel.app/" rel="noopener noreferrer"&gt;KM/OS&lt;/a&gt;, my portfolio: instead of a page that &lt;em&gt;tells&lt;/em&gt; you I can build things, it's a desktop OS running in a browser tab: windows, a dock, a real virtual filesystem behind a Terminal app, a menu bar where every section of a normal portfolio is an application that actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  No framework, on purpose
&lt;/h2&gt;

&lt;p&gt;Vanilla JavaScript in ES modules, bundled by &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; into a single file. Hand-written CSS over one set of design tokens. Two dependencies total, and both only at build time.&lt;/p&gt;

&lt;p&gt;For an interface built almost entirely from bespoke pieces (custom window chrome, custom drag/resize physics, a custom terminal emulator), a framework would have added a runtime, a build step, and a set of conventions in exchange for very little the project actually needed. A few decisions that came out of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows are cloned from a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; and driven by state classes, so all the open/close/minimize motion lives in CSS, not JS.&lt;/li&gt;
&lt;li&gt;Pointer Events handle every gesture (drag, resize, swipe), so behavior is identical across mouse, pen, and touch without separate code paths.&lt;/li&gt;
&lt;li&gt;One virtual filesystem is shared by the Terminal, a Finder-style file explorer, and a notes app. Edit a file in one, and it changes in the others.&lt;/li&gt;
&lt;li&gt;A service worker uses content-hashed bundle URLs (&lt;code&gt;app.js?b=&amp;lt;build-id&amp;gt;&lt;/code&gt;, regenerated every build). This came from a real bug: a stale cached bundle once ran against a freshly-deployed HTML document and silently broke the mobile UI, because the old JS was looking for DOM nodes the new shell no longer had. Giving the bundle's address a build id, not just its cache headers, means a stale cache has nothing to serve at a URL it's never seen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What leaves the browser
&lt;/h2&gt;

&lt;p&gt;Worth being explicit about, since it's a portfolio and not a toy: only two things call out to anything else. A question typed into the built-in AI assistant goes to the site's own server, then to a language-model provider. The API key never reaches the browser. The contact form posts to a forwarding service. Everything else is same-origin, enforced by a real &lt;code&gt;Content-Security-Policy&lt;/code&gt; HTTP header (not a meta tag: meta tags can't express &lt;code&gt;frame-ancestors&lt;/code&gt;), which is also why there's no analytics script and no font CDN.&lt;/p&gt;

&lt;p&gt;Two inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags that do need to run are pinned by SHA-256 hash instead of &lt;code&gt;unsafe-inline&lt;/code&gt;, and there's a test that recomputes both hashes from the HTML source and fails the build loudly if either script's content ever changes without the hash being updated. That's a tripwire against the failure mode where an edited script just silently stops executing in production with zero build error.&lt;/p&gt;

&lt;p&gt;There's a public chat room too, which is the one place text from strangers gets rendered back to other visitors. Messages are length-capped, stripped of control characters, rate-limited server-side, and always written into the DOM as text content, never as markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I didn't expect to spend time on: GEO
&lt;/h2&gt;

&lt;p&gt;Once the site worked, the next problem was that nobody could find it: a name search for "Karim Masmoudi" is dominated by an unrelated, actively-covered public figure with years of accumulated backlinks. That's not a code problem. But there &lt;em&gt;was&lt;/em&gt; a real code problem hiding next to it: the site was shipping &lt;code&gt;noindex, nofollow&lt;/code&gt; on every page, which meant none of the SEO work mattered until that came off first.&lt;/p&gt;

&lt;p&gt;After that, the more interesting part was optimizing for something that barely existed as a discipline a year ago: being &lt;em&gt;cited&lt;/em&gt; by AI answer engines (ChatGPT, Perplexity, Google AI Overviews), not just ranked by classic search. Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;FAQPage&lt;/code&gt; JSON-LD block, but built from real, already-written content rather than freehand copy: I split the AI assistant's own one-paragraph bio into sentences and mapped them to its own suggested questions, so the "answers" are literally the same facts the assistant already gives a human visitor.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;/.well-known/ai.txt&lt;/code&gt;, &lt;code&gt;/ai/summary.json&lt;/code&gt;, &lt;code&gt;/ai/faq.json&lt;/code&gt;: the GEO-era equivalents of &lt;code&gt;robots.txt&lt;/code&gt;, generated at build time from the same data files, not maintained by hand.&lt;/li&gt;
&lt;li&gt;A JSON-LD &lt;code&gt;@graph&lt;/code&gt; where entities reference each other by &lt;code&gt;@id&lt;/code&gt; (Person → CollegeOrUniversity → WebSite) instead of nesting everything inline, since that's what lets a search engine resolve "who is this person" as a connected set of facts instead of a string that happens to appear on a page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dateModified&lt;/code&gt; sourced from &lt;code&gt;git log -1&lt;/code&gt;, not a hardcoded string that silently goes stale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one thing I deliberately &lt;em&gt;didn't&lt;/em&gt; add: a &lt;code&gt;SearchAction&lt;/code&gt; schema advertising site search to Google, because the site doesn't support URL-parameter search deep-linking. A rich-result annotation for a capability that doesn't work is worse than no annotation at all.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
