<?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: NAGARAMPALLLI SARVAN KUMAR</title>
    <description>The latest articles on DEV Community by NAGARAMPALLLI SARVAN KUMAR (@sarvan2187).</description>
    <link>https://dev.to/sarvan2187</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%2F3953524%2F2d5b645d-6d7c-43a4-877b-79cda704f5f8.png</url>
      <title>DEV Community: NAGARAMPALLLI SARVAN KUMAR</title>
      <link>https://dev.to/sarvan2187</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sarvan2187"/>
    <language>en</language>
    <item>
      <title>The scoring bugs we almost shipped in a hackathon judging platform</title>
      <dc:creator>NAGARAMPALLLI SARVAN KUMAR</dc:creator>
      <pubDate>Fri, 02 Oct 2026 05:41:52 +0000</pubDate>
      <link>https://dev.to/sarvan2187/the-scoring-bugs-we-almost-shipped-in-a-hackathon-judging-platform-47c7</link>
      <guid>https://dev.to/sarvan2187/the-scoring-bugs-we-almost-shipped-in-a-hackathon-judging-platform-47c7</guid>
      <description>&lt;p&gt;For DOGFOOD 2026, Hackathon Raptors gave teams an odd brief: build the platform they'll run their own hackathons on. Registration, teams, submissions, judge assignment, scoring, results, certificates, an archive of past events. We're Team CodeHawk, and our entry is HackFlow, a self-hosted platform that comes up with a single &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I could walk you through the features, but the parts worth writing about are the four places where something we thought was simple turned out to be wrong. Three of them sit in the code that decides who wins.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/sarvan-2187/dog-food" rel="noopener noreferrer"&gt;https://github.com/sarvan-2187/dog-food&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One database, on purpose
&lt;/h2&gt;

&lt;p&gt;The decision that shaped the rest of the project was keeping everything in one place: one FastAPI process and one Postgres database. The Docker build has two stages. Node builds the React app and then gets thrown away, and the slim Python image that's left serves both the API and the frontend. At runtime there are two containers in total.&lt;/p&gt;

&lt;p&gt;It would be easy to read that as a shortcut, so here's the reasoning. A hackathon platform has to hold rules like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nobody saves a submission after the deadline&lt;/li&gt;
&lt;li&gt;a rubric can't change once a judge has scored against it&lt;/li&gt;
&lt;li&gt;one team per person per event&lt;/li&gt;
&lt;li&gt;one vote per person per project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those is a check that needs to see consistent data at the exact moment someone writes. If teams, submissions and scoring lived in separate services, every rule would turn into a distributed consistency problem. With one database, each one is a query inside a transaction, and usually a unique index backs it up. Almost every later decision got easier because we made this one early.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoring algorithm we almost shipped
&lt;/h2&gt;

&lt;p&gt;Judges don't grade the same way. One gives everything a 90, another treats 6 out of 10 as high praise. If you average raw scores, the projects that happened to draw the generous judge win, and that's luck.&lt;/p&gt;

&lt;p&gt;So HackFlow converts each judge's totals into z-scores against that judge's own mean and spread, then averages them per project. A score stops meaning "8 out of 10" and starts meaning "well above what this judge usually gives."&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%2Fin9mrn6c10q2gbwwfvgx.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%2Fin9mrn6c10q2gbwwfvgx.png" alt="A judge's score form in HackFlow, with each criterion's weight and scale" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the textbook version, and we had it working. Then, on the day of the deadline, we audited our own implementation against the official fixture data (40 projects, 30 judges, 123 scores) and found it was wrong in ways none of our tests had caught.&lt;/p&gt;

&lt;p&gt;To be clear about the stakes: this was all test data. HackFlow hadn't run a real event yet, so no published result was ever wrong, and every fix below is in the version we submitted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 1: the weights didn't mean what the organizer typed
&lt;/h3&gt;

&lt;p&gt;We calculated the raw total as &lt;code&gt;Σ weight × value&lt;/code&gt;. That works as long as every criterion uses the same scale. Now picture an organizer who sets Technical at 0 to 10 with a weight of 70%, and Presentation at 0 to 100 with a weight of 30%. Presentation ends up carrying about 81% of the total.&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%2Fvn4wl5wbiayj5pzhhn7m.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%2Fvn4wl5wbiayj5pzhhn7m.png" alt="The rubric builder: every criterion has its own weight and its own max score" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing throws an error, and the screen still says 70/30, but the ranking is following a split nobody chose. The fix was to scale each value by its own maximum before weighting, then divide by the sum of the weights. When every criterion shares a scale, the new formula gives exactly the same answer as the old one. That's the uncomfortable part: all our fixtures used one scale, which is why our tests never noticed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 2: judges with nothing to say were dragging scores down
&lt;/h3&gt;

&lt;p&gt;A judge who scored only one project, or gave every project 4/4/4, has a spread of zero. We'd avoided the divide-by-zero by giving those judges a z-score of 0, and then we averaged that 0 in with everyone else's.&lt;/p&gt;

&lt;p&gt;That pulls a project's score toward zero in proportion to how many of those judges it happened to draw. Two projects with the same real evidence could rank differently purely because of who was assigned to them. On the fixture data, 12 projects were in the wrong position because of this. Now a judge whose scores say nothing about relative order is left out of the average entirely, instead of being counted as a vote for "average."&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 3: floating point invented an opinion
&lt;/h3&gt;

&lt;p&gt;We checked for zero spread with &lt;code&gt;σ == 0&lt;/code&gt;. Weighted totals produce values like &lt;code&gt;0.1 × 3&lt;/code&gt; and &lt;code&gt;0.3&lt;/code&gt;, which aren't equal in floating point. So a judge who gave identical marks looked like they had a tiny spread, and dividing by that tiny spread turned rounding error into z-scores of +1 and -1. That's a full point of normalized score, out of nothing. We now compare against a relative tolerance, &lt;code&gt;σ &amp;gt; 1e-9 × max(1, |μ|)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There was a smaller one hiding in our seed data, too. The rubric weights were 0.3333, 0.3333 and 0.3334, which was enough to split real ties by a ten-thousandth. Two projects tied at 4.33 showed up as first and second. Exact thirds fixed it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed once it was right
&lt;/h3&gt;

&lt;p&gt;With the bugs fixed, normalization moved the standings more than we expected. Only 4 of the 40 fixture projects kept the rank a plain average would give them, and 9 of the top 10 moved. That movement is the whole point of normalizing. It shows how much judge harshness was shaping the raw order. Dry Harbour went from 31st on raw scores to 9th. Its judges were tough on everyone, and it was one of the best things they saw.&lt;/p&gt;

&lt;p&gt;The lesson I'd pass on: a test that recomputes your formula with your formula can't tell you the formula is wrong. The test that finally earned its keep rebuilds the standings from the raw fixture file using a separate implementation, then checks the live results endpoint against it. Every bug above now has its own test, written to fail on the old behavior, so none of them can come back quietly. We trust the rankings more now than we did before the audit, because we've watched them fail and know why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Postgres lock that hung our test suite
&lt;/h2&gt;

&lt;p&gt;We didn't use a migration framework. Instead, a few idempotent steps run every time the app boots, so running &lt;code&gt;docker compose up&lt;/code&gt; on an old database upgrades it in place. The obvious way to add a column looks harmless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;session_version&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks safe to run on every boot, and it isn't. Postgres takes an exclusive lock on the table before it checks whether the column already exists. Our test suite keeps transactions open, so the app's startup sat waiting for that lock and the whole suite hung. There was no error message, just nothing happening.&lt;/p&gt;

&lt;p&gt;The fix is to ask &lt;code&gt;information_schema&lt;/code&gt; whether the column exists first, and only run the &lt;code&gt;ALTER&lt;/code&gt; when it's actually missing. We applied the same caution to a new unique index enforcing one team per person per event: it only gets created if the existing data already satisfies it. If it doesn't, the app still boots, logs the duplicates, and lists them on the admin page so a person can sort them out. We didn't want a boot step that could fail on old data to take an event offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disqualification is harder than hiding a row
&lt;/h2&gt;

&lt;p&gt;The brief says organizers can disqualify an entry. We assumed that meant hiding it from the results.&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%2Ff4ejjoer1y10mfinvlpo.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%2Ff4ejjoer1y10mfinvlpo.png" alt="The organizer's eligibility panel: automatic checks flag, a human disqualifies" width="799" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Normalization makes that wrong. Say a judge gave the disqualified entry a 1 out of 10. That score is part of the judge's mean. If you only hide the row, the 1 keeps dragging the judge's mean down, and every other project that judge scored looks a little better than it should. Removing one entry would quietly change the scores of projects that had nothing to do with it.&lt;/p&gt;

&lt;p&gt;So HackFlow drops the entry's scores before normalization runs, and recomputes every judge's mean and spread as if that entry had never been judged. The scores themselves are kept, so if an organizer reinstates the entry, the next read of the results puts everything back.&lt;/p&gt;

&lt;p&gt;The same reasoning decided how the judging deadline works. It's soft on purpose: a late score is recorded and flagged, never refused. Normalization can correct for a harsh judge, but it can't make up a review that never happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we haven't solved
&lt;/h2&gt;

&lt;p&gt;Two limits are still there, and we'd rather name them than have someone find them.&lt;/p&gt;

&lt;p&gt;A judge who scored exactly two projects always produces +1 and -1, whether the gap was 4.9 against 5.0 or 1 against 5. That comes with per-judge z-scores on small samples. The real remedy is giving each judge more entries, which is what the assignment step's judges-per-submission setting is for.&lt;/p&gt;

&lt;p&gt;Normalization also only removes a judge's general harshness. It does nothing about a judge who favors one particular project. Catching collusion needs a different tool, and for now that's the per-judge CSV export and the audit log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack and credits
&lt;/h2&gt;

&lt;p&gt;Python 3.12, FastAPI, SQLModel, PostgreSQL 16, React 18 with TypeScript, pytest and Playwright, all run with Docker Compose. We built HackFlow with help from Claude Code, Anthropic's AI coding agent, and say so in the README. The decisions are ours, and so are the bugs.&lt;/p&gt;

&lt;p&gt;What we ended up with is a platform that runs a whole event, from sign-up to certificates and an archive, from one &lt;code&gt;docker compose up&lt;/code&gt; on seeded data, and it passes all 7 of the official DOGFOOD checks. The judging part is the piece we're proudest of, mostly because of how much it taught us.&lt;/p&gt;

&lt;p&gt;If you're building anything that turns people's scores into a ranking, test it with criteria on different scales and with a judge who gives everyone the same mark. Those two cases would have caught our two worst bugs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Team CodeHawk&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>postgres</category>
      <category>webdev</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>I Got Tired of Messy Bookmark Managers, So I Built My Own</title>
      <dc:creator>NAGARAMPALLLI SARVAN KUMAR</dc:creator>
      <pubDate>Wed, 27 May 2026 06:18:29 +0000</pubDate>
      <link>https://dev.to/sarvan2187/i-built-bookmrk-an-open-source-local-first-bookmark-manager-for-power-users-41g0</link>
      <guid>https://dev.to/sarvan2187/i-built-bookmrk-an-open-source-local-first-bookmark-manager-for-power-users-41g0</guid>
      <description>&lt;p&gt;I had hundreds of bookmarks.&lt;/p&gt;

&lt;p&gt;Most of them were practically useless because I could never actually find them when I needed them.&lt;/p&gt;

&lt;p&gt;I tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser folders&lt;/li&gt;
&lt;li&gt;Notion&lt;/li&gt;
&lt;li&gt;Raindrop&lt;/li&gt;
&lt;li&gt;tab hoarding&lt;/li&gt;
&lt;li&gt;“read later” apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But eventually everything became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cluttered&lt;/li&gt;
&lt;li&gt;slow&lt;/li&gt;
&lt;li&gt;over-organized&lt;/li&gt;
&lt;li&gt;or dependent on cloud sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something faster.&lt;/p&gt;

&lt;p&gt;Something that felt more like a developer tool than a traditional bookmark manager.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Bookmrk&lt;/strong&gt;.&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%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fhome-4-up.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="450" alt="Bookmrk Home" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fhome-4-up.png"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Canvas-style bookmark organization built for developers and power users.&lt;/p&gt;

&lt;p&gt;A lightweight bookmark manager with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instant quick-save shortcuts&lt;/li&gt;
&lt;li&gt;canvas-style organization&lt;/li&gt;
&lt;li&gt;drag-and-drop boards&lt;/li&gt;
&lt;li&gt;multiple visual themes&lt;/li&gt;
&lt;li&gt;Chrome bookmark import&lt;/li&gt;
&lt;li&gt;export/import support&lt;/li&gt;
&lt;li&gt;and a workflow designed for people who live in the browser.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introducing Bookmrk
&lt;/h2&gt;

&lt;p&gt;Bookmrk is an open-source bookmark manager built using React, TypeScript, Vite, Zustand, DnD-kit and Tailwind CSS.&lt;/p&gt;

&lt;p&gt;GitHub Repository:&lt;br&gt;
&lt;a href="https://github.com/sarvan-2187/Bookmrk" rel="noopener noreferrer"&gt;https://github.com/sarvan-2187/Bookmrk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live Website:&lt;br&gt;
&lt;a href="https://sarvan-2187.github.io/bookmrk-web/" rel="noopener noreferrer"&gt;https://sarvan-2187.github.io/bookmrk-web/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project focuses heavily on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local-first workflows&lt;/li&gt;
&lt;li&gt;keyboard productivity&lt;/li&gt;
&lt;li&gt;clean information architecture&lt;/li&gt;
&lt;li&gt;and fast interaction patterns.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  The Core Idea
&lt;/h2&gt;

&lt;p&gt;Instead of treating bookmarks like an endless vertical list, Bookmrk organizes them into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages&lt;/li&gt;
&lt;li&gt;Boards&lt;/li&gt;
&lt;li&gt;Bookmark Cards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Trello + browser bookmarks + keyboard-first workflows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The interface uses a &lt;strong&gt;canvas-style 4-column board system&lt;/strong&gt; where boards can be freely rearranged.&lt;/p&gt;

&lt;p&gt;This makes large bookmark collections significantly easier to navigate visually.&lt;/p&gt;


&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Quick Save Workflow
&lt;/h3&gt;

&lt;p&gt;One of the main ideas behind Bookmrk was reducing friction.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;opening a bookmark manager&lt;/li&gt;
&lt;li&gt;creating folders&lt;/li&gt;
&lt;li&gt;manually saving links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can simply press:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ctrl/Cmd + Shift + Y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the current page instantly gets saved into a &lt;code&gt;Quick Saves&lt;/code&gt; board.&lt;/p&gt;




&lt;h3&gt;
  
  
  Keyboard-Driven Productivity
&lt;/h3&gt;

&lt;p&gt;Bookmrk includes several keyboard shortcuts designed for power users.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shortcut&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl/Cmd + Shift + Y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quick Save&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle Sidebar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;P&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create New Page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;I&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open Settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle Privacy Blur&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open Search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl/Cmd + K&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toolbar Search&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The goal was to make navigation feel closer to tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raycast&lt;/li&gt;
&lt;li&gt;Arc&lt;/li&gt;
&lt;li&gt;Linear&lt;/li&gt;
&lt;li&gt;VSCode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;rather than traditional bookmark apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Canvas-Style Organization
&lt;/h2&gt;

&lt;p&gt;One of my favorite parts of the project is the board system.&lt;/p&gt;

&lt;p&gt;Boards can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;move across columns&lt;/li&gt;
&lt;li&gt;reorder dynamically&lt;/li&gt;
&lt;li&gt;act as visual bookmark groups&lt;/li&gt;
&lt;li&gt;support drag-and-drop placement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike traditional nested folders, this layout provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spatial memory&lt;/li&gt;
&lt;li&gt;visual grouping&lt;/li&gt;
&lt;li&gt;faster scanning&lt;/li&gt;
&lt;li&gt;less cognitive overload&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Home Layout
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Home Layout
&lt;/h3&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%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fhome-4-up.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="450" alt="Home — 4 column grid" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fhome-4-up.png"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Modern 4-column canvas organization system&lt;/p&gt;




&lt;h3&gt;
  
  
  Quick Save Modal
&lt;/h3&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%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fquick-save-modal.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="450" alt="Quick Save Modal" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fquick-save-modal.png"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Fast keyboard-driven bookmark capture&lt;/p&gt;




&lt;h3&gt;
  
  
  Board Settings
&lt;/h3&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%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fboard-settings-modal.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="450" alt="Board Settings Modal" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fboard-settings-modal.png"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Board management and organization controls&lt;/p&gt;




&lt;h2&gt;
  
  
  Multiple Built-In Themes
&lt;/h2&gt;

&lt;p&gt;Bookmrk currently ships with 3 themes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discord&lt;/li&gt;
&lt;li&gt;Simple&lt;/li&gt;
&lt;li&gt;NeoBrutalist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each theme changes the entire visual feel of the application.&lt;/p&gt;




&lt;h3&gt;
  
  
  Discord Theme
&lt;/h3&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%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fdiscord-theme.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="450" alt="Discord Theme" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fdiscord-theme.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Simple Theme
&lt;/h3&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%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fsimple-theme.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="450" alt="Simple Theme" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fsimple-theme.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  NeoBrutalist Theme
&lt;/h3&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%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fneobrutalist-theme.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="450" alt="NeoBrutalist Theme" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsarvan-2187%2FBookmrk%2Frefs%2Fheads%2Fmain%2Fdocs%2Fscreenshots%2Fneobrutalist-theme.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Decisions
&lt;/h2&gt;

&lt;p&gt;I wanted the architecture to remain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modular&lt;/li&gt;
&lt;li&gt;lightweight&lt;/li&gt;
&lt;li&gt;maintainable&lt;/li&gt;
&lt;li&gt;extension-friendly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State Management - Zustand
&lt;/h3&gt;

&lt;p&gt;I used Zustand because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;minimal boilerplate&lt;/li&gt;
&lt;li&gt;simple global state&lt;/li&gt;
&lt;li&gt;easier mental model&lt;/li&gt;
&lt;li&gt;excellent developer experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The store handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pages&lt;/li&gt;
&lt;li&gt;boards&lt;/li&gt;
&lt;li&gt;bookmarks&lt;/li&gt;
&lt;li&gt;themes&lt;/li&gt;
&lt;li&gt;settings&lt;/li&gt;
&lt;li&gt;modals&lt;/li&gt;
&lt;li&gt;toast notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without requiring Redux-level complexity.&lt;/p&gt;




&lt;h3&gt;
  
  
  Drag &amp;amp; Drop - DnD-kit
&lt;/h3&gt;

&lt;p&gt;The canvas organization system uses:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@dnd-kit/core
@dnd-kit/sortable
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fluid drag interactions&lt;/li&gt;
&lt;li&gt;board insertion logic&lt;/li&gt;
&lt;li&gt;cross-column movement&lt;/li&gt;
&lt;li&gt;reorder animations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One challenge here was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;supporting insertion into empty columns while preserving intuitive positioning behavior.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Storage Strategy - Local First
&lt;/h3&gt;

&lt;p&gt;Bookmrk uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome extension storage APIs when available&lt;/li&gt;
&lt;li&gt;localStorage fallback otherwise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser-extension support&lt;/li&gt;
&lt;li&gt;standalone web usage&lt;/li&gt;
&lt;li&gt;portability&lt;/li&gt;
&lt;li&gt;offline-first behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I intentionally avoided making cloud sync mandatory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Challenges
&lt;/h2&gt;

&lt;p&gt;Some interesting engineering problems during development:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Chrome Import Deduplication
&lt;/h3&gt;

&lt;p&gt;When importing Chrome bookmarks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate imports needed prevention&lt;/li&gt;
&lt;li&gt;but users also needed re-import support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;persisted import flags&lt;/li&gt;
&lt;li&gt;automatic reset when imported boards are deleted&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Dragging Across Empty Columns
&lt;/h3&gt;

&lt;p&gt;Traditional DnD systems often assume populated containers.&lt;/p&gt;

&lt;p&gt;Supporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;empty lanes&lt;/li&gt;
&lt;li&gt;dynamic insertion&lt;/li&gt;
&lt;li&gt;cross-column sorting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;required additional placement logic and collision handling.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Theme Consistency
&lt;/h3&gt;

&lt;p&gt;Supporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;light mode&lt;/li&gt;
&lt;li&gt;dark mode&lt;/li&gt;
&lt;li&gt;NeoBrutalist mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while keeping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modal readability&lt;/li&gt;
&lt;li&gt;accessibility&lt;/li&gt;
&lt;li&gt;contrast consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;became surprisingly complex.&lt;/p&gt;




&lt;h2&gt;
  
  
  Privacy &amp;amp; Local-First Philosophy
&lt;/h2&gt;

&lt;p&gt;A major design principle behind Bookmrk is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your bookmarks should belong to you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data stays local&lt;/li&gt;
&lt;li&gt;no forced accounts&lt;/li&gt;
&lt;li&gt;no mandatory cloud sync&lt;/li&gt;
&lt;li&gt;no external tracking layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Export/import support also makes the data portable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Future Roadmap
&lt;/h2&gt;

&lt;p&gt;Some things I want to explore next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser sync&lt;/li&gt;
&lt;li&gt;Firefox support&lt;/li&gt;
&lt;li&gt;command palette improvements&lt;/li&gt;
&lt;li&gt;AI-assisted bookmark organization&lt;/li&gt;
&lt;li&gt;advanced search indexing&lt;/li&gt;
&lt;li&gt;bookmark tagging&lt;/li&gt;
&lt;li&gt;collaborative boards&lt;/li&gt;
&lt;li&gt;plugin system&lt;/li&gt;
&lt;li&gt;markdown support&lt;/li&gt;
&lt;li&gt;smarter import/export tools&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Open Source &amp;amp; Contributions
&lt;/h2&gt;

&lt;p&gt;Bookmrk is fully open-source and contributions are welcome.&lt;/p&gt;

&lt;p&gt;If you're interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend engineering&lt;/li&gt;
&lt;li&gt;Chrome extension development&lt;/li&gt;
&lt;li&gt;UX systems&lt;/li&gt;
&lt;li&gt;state management&lt;/li&gt;
&lt;li&gt;accessibility&lt;/li&gt;
&lt;li&gt;productivity tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;feel free to contribute.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/sarvan-2187/Bookmrk" rel="noopener noreferrer"&gt;https://github.com/sarvan-2187/Bookmrk&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Bookmrk started as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I just want a faster way to manage bookmarks.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But it slowly evolved into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a productivity system&lt;/li&gt;
&lt;li&gt;a browser workflow tool&lt;/li&gt;
&lt;li&gt;a design experiment&lt;/li&gt;
&lt;li&gt;and a deeper exploration of local-first UX.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s still a lot to improve, but the project has already taught me a lot about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;state architecture&lt;/li&gt;
&lt;li&gt;interaction design&lt;/li&gt;
&lt;li&gt;drag-and-drop systems&lt;/li&gt;
&lt;li&gt;extension APIs&lt;/li&gt;
&lt;li&gt;and developer-focused product design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the project sounds interesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;try it out&lt;/li&gt;
&lt;li&gt;open issues&lt;/li&gt;
&lt;li&gt;contribute&lt;/li&gt;
&lt;li&gt;or drop feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you like the idea, consider starring the repo ⭐&lt;/p&gt;




&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/sarvan-2187/Bookmrk" rel="noopener noreferrer"&gt;https://github.com/sarvan-2187/Bookmrk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live Demo:&lt;br&gt;
&lt;a href="https://sarvan-2187.github.io/bookmrk-web/" rel="noopener noreferrer"&gt;https://sarvan-2187.github.io/bookmrk-web/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  opensource #webdev #react #typescript #productivity #chrome #javascript #github
&lt;/h1&gt;

</description>
      <category>opensource</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>react</category>
    </item>
  </channel>
</rss>
