[{"data":1,"prerenderedAt":153},["ShallowReactive",2],{"profile":3,"projects:page:\u002Fprojects\u002Fclairwire":22,"projects:docs:\u002Fprojects\u002Fclairwire":126,"structured-data:current-stack":140},{"id":4,"alias":5,"current":6,"email":10,"extension":11,"headline":12,"links":13,"location":17,"meta":18,"name":19,"stem":20,"__hash__":21},"profile\u002Fprofile.yml","readonly",{"role":7,"company":8,"companyUrl":9},"Fullstack Engineer — Tech Lead","DataCose","https:\u002F\u002Fdatacose.com","redonemini18@gmail.com","yml","Fullstack Engineer",{"github":14,"linkedin":15,"twitch":16},"https:\u002F\u002Fgithub.com\u002FRedoni18","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fredon-emini\u002F","https:\u002F\u002Fwww.twitch.tv\u002Fr3adonly","Pristina, Kosovo",{},"Redon Emini","profile","Fbb1IAp4nuUOOtgXdwpzcqSmG0CUKtZ4D0ifPqdK5QU",{"id":23,"title":24,"body":25,"description":102,"extension":103,"featured":104,"kind":105,"links":106,"meta":108,"navigation":104,"order":97,"path":109,"period":110,"preview":111,"seo":115,"stack":116,"stem":123,"year":124,"__hash__":125},"projects\u002Fprojects\u002Fclairwire\u002Findex.md","Clairwire",{"type":26,"value":27,"toc":95},"minimark",[28,32,35,40,43,68,71,75,78,81,84,90],[29,30,31],"p",{},"Clairwire is a media monitoring tool for PR agencies and in-house comms teams. It collects everything said about the people and brands a team represents, from news sites, X, Bluesky, YouTube, Reddit, RSS feeds, blogs and a few other sources, and puts it all in one place for each client. When something needs attention, like a story that's picking up speed or a critical piece from an outlet people trust, it sends an alert.",[29,33,34],{},"Without a tool like this, keeping up with a client means saved searches on every platform, a lot of open tabs, and a real chance of missing the one post that matters. Clairwire replaces that with a single feed per client. Each mention is tagged with its sentiment, how credible the source is and how fast it's spreading, and each morning the team gets a short written summary of what happened and what, if anything, to do about it.",[36,37,39],"h2",{"id":38},"whats-under-the-hood","What's under the hood",[29,41,42],{},"It's a TypeScript monorepo with three parts:",[44,45,46,55,61],"ul",{},[47,48,49,50,54],"li",{},"a ",[51,52,53],"strong",{},"NestJS API"," that owns the Postgres database (through Prisma) and does all the background work,",[47,56,49,57,60],{},[51,58,59],{},"Nuxt 4 dashboard"," that talks to the API over HTTP,",[47,62,63,64,67],{},"a small ",[51,65,66],{},"shared package"," with the types both of them depend on.",[29,69,70],{},"Most of what the product does isn't triggered by someone clicking a button. Sources are polled on a schedule, new mentions get analyzed, alert rules get evaluated, and briefs get written before the team starts work. All of that runs as jobs on BullMQ queues backed by Redis. The HTTP API mostly reads the results and adds new jobs.",[36,72,74],{"id":73},"the-two-problems-worth-writing-about","The two problems worth writing about",[29,76,77],{},"Two parts of this turned out to be more interesting than any single integration.",[29,79,80],{},"The first is keeping a long list of very different platforms from turning the codebase into a long list of special cases. The answer was one strict data contract that every source has to produce, and a pipeline that never asks where a mention came from.",[29,82,83],{},"The second is relevance. A search result that contains your client's name isn't the same thing as a story about your client, and treating the two as equal quietly corrupts every number on the dashboard. Fixing that meant changing what the system counts, and then shipping the change without making months of history disappear.",[85,86],"link-card",{"description":87,"title":88,"to":89},"Adapters, capability flags, job queues, and an AI layer you can swap or mock.","One contract, many sources","\u002Fprojects\u002Fclairwire\u002Farchitecture",[85,91],{"description":92,"title":93,"to":94},"Deciding whether a mention is actually about a client, and migrating the data without emptying anyone's dashboard.","Relevance, not keyword hits","\u002Fprojects\u002Fclairwire\u002Frelevance",{"title":96,"searchDepth":97,"depth":98,"links":99},"",2,3,[100,101],{"id":38,"depth":97,"text":39},{"id":73,"depth":97,"text":74},"Media monitoring for PR teams. Everything said about your clients in one place, and an alert when it matters.","md",true,"personal",{"live":107},"https:\u002F\u002Fclairwire.com",{},"\u002Fprojects\u002Fclairwire","2026 — Present",{"src":112,"alt":113,"og":114},"\u002Fprojects\u002Fclairwire\u002Fpreview.webp","Clairwire: 'Every mention. Understood.' beside an analysis card showing sentiment, credibility and virality scores with a plain-language reason","\u002Fprojects\u002Fclairwire\u002Fhero.png",{"title":24,"description":102},[117,118,119,120,121,122],"Nuxt 4","NestJS","PostgreSQL","Redis","BullMQ","Prisma","projects\u002Fclairwire\u002Findex","2026","K7nTalJuvVoa5ar4T204mXZPQYHUxRfUZ2jIxsERkXY",[127,129,135],{"path":109,"title":24,"stem":123,"navigation":104,"description":102,"preview":128},{"src":112,"alt":113,"og":114},{"path":89,"title":88,"stem":130,"navigation":131,"description":133,"preview":134},"projects\u002Fclairwire\u002F1.architecture",{"title":132},"Architecture","How Clairwire stops a dozen different platforms from becoming a dozen special cases, with one normalized shape, adapters, capability flags, job queues and an AI layer behind an interface.",null,{"path":94,"title":93,"stem":136,"navigation":137,"description":139,"preview":134},"projects\u002Fclairwire\u002F2.relevance",{"title":138},"Relevance","Why a mention containing a client's name isn't the same as a mention about them, how Clairwire records the difference, and how the change shipped without emptying anyone's dashboard.",[141,142,143,144,145,146,147,148,149,119,150,151,152],"TypeScript","Vue 3","Nuxt","Pinia","Tailwind CSS","Python","FastAPI","SQLAlchemy","Pydantic","Supabase","GCP","Firebase",1790468886577]