[{"data":1,"prerenderedAt":153},["ShallowReactive",2],{"project:zakonik":3},{"id":4,"title":5,"body":6,"demo":116,"description":117,"docs":116,"draft":118,"extension":119,"highlights":120,"image":130,"language":131,"meta":132,"navigation":133,"order":134,"path":135,"repo":116,"seo":136,"site":137,"stack":138,"stem":145,"tagline":146,"tags":147,"__hash__":152},"projects\u002Fprojects\u002Fzakonik.md","Zakonik",{"type":7,"value":8,"toc":108},"minimark",[9,14,31,47,50,54,57,69,72,76,91,94,98,101],[10,11,13],"h2",{"id":12},"the-problem-it-solves","The problem it solves",[15,16,17,18,22,23,26,27,30],"p",{},"Type ",[19,20,21],"em",{},"otkaz na bolovanju"," into any Serbian legal search and you get nothing.\nNot because the law is silent, but because the statute never uses the word\n",[19,24,25],{},"bolovanje"," — it says ",[19,28,29],{},"privremena sprečenost za rad usled bolesti",". Not one\nword in common, so keyword search returns an empty page while the answer sits\nin article 183 of the Labour Act.",[15,32,33,34,37,38,42,43,46],{},"The gap is vocabulary, not knowledge. People know what happened to them; they\ncannot guess the term it was filed under. Measured against the corpus, the word\n",[19,35,36],{},"predomislio"," (\"changed my mind\") appears in ",[39,40,41],"strong",{},"none"," of the 5,056 indexed\narticles — the law calls it ",[19,44,45],{},"odustanak od ugovora",".",[15,48,49],{},"So the question is translated into statutory vocabulary first, and only then\nsearched. That single step is what replaces embeddings.",[10,51,53],{"id":52},"no-vector-database","No vector database",[15,55,56],{},"The first build did it the orthodox way: Postgres, pgvector, an embedding per\narticle. It worked, and for a project with no budget it meant a database that\nidles into suspension, a monthly bill, and one more service that can fail.",[15,58,59,60,63,64,68],{},"The whole corpus is now a single ",[39,61,62],{},"8 MB SQLite file committed to the repo"," and\nread in-process through ",[65,66,67],"code",{},"node:sqlite",", with FTS5 doing retrieval in about\n0.1 ms. Deploying needs exactly one environment variable.",[15,70,71],{},"Honest caveat, because it matters: in my own measurements vectors still ranked\nthe correct article first slightly more often. SQLite is cheaper and sufficient\nhere — not equivalent.",[10,73,75],{"id":74},"citations-are-verified-not-trusted","Citations are verified, not trusted",[15,77,78,79,82,83,86,87,90],{},"The model is given the retrieved articles and told to use nothing else, with a\nsanctioned way to refuse. That instruction is ",[39,80,81],{},"not trusted",". After generation,\ncode extracts every ",[65,84,85],{},"[Zakon o radu, član 183]"," citation and checks it against\nthe articles actually retrieved; anything unmatched is flagged in the UI as\n",[19,88,89],{},"nepotvrđen citat",". The legal disclaimer is appended by the application, so the\nmodel cannot reword or drop it.",[15,92,93],{},"Refusal is treated as a feature. Ask something outside the corpus and it says\nso rather than assembling a confident-sounding paragraph — which, for legal\nquestions, is the difference between useless and harmful.",[10,95,97],{"id":96},"built-in-a-day","Built in a day",[15,99,100],{},"First commit to live site inside a day, written with Claude Code.\nWhat that time actually bought was not the UI but the parts that are easy to\nskip: the citation verifier, graceful degradation to the retrieved articles\nwhen the upstream model quota runs out, and a privacy page that states plainly\nthat questions are sent to Google.",[15,102,103,104,107],{},"Repealed articles are a good example of how narrow the margin is. The corpus\nkeeps them, because a statute printed with silent gaps is harder to read — but\nretrieval excludes anything opening with ",[65,105,106],{},"(Brisano)",", so a dead provision can\nnever end up in an answer. The filter matches on that prefix rather than the\nword anywhere in the text: 23 articles are genuinely repealed, while 14 live\nones merely mention it, including one about the property of a struck-off\ncompany. Catching that distinction is the difference between fixing a bug and\nquietly deleting fourteen valid articles.",{"title":109,"searchDepth":110,"depth":110,"links":111},"",2,[112,113,114,115],{"id":12,"depth":110,"text":13},{"id":52,"depth":110,"text":53},{"id":74,"depth":110,"text":75},{"id":96,"depth":110,"text":97},null,"Serbian legislation searched in plain language, answered with the exact article it rests on. Thirteen statutes and 5,056 articles indexed, every citation verified against the retrieved text before it reaches the page.",false,"md",[121,124,127],{"value":122,"label":123},"5,056","articles across 13 statutes, in one 8 MB SQLite file",{"value":125,"label":126},"~0.1ms","lexical retrieval, in-process, no database server",{"value":128,"label":129},"100%","of citations checked against the retrieved text, in code","\u002Fprojects\u002Fzakonik.webp","TypeScript",{},true,1.1,"\u002Fprojects\u002Fzakonik",{"title":5,"description":117},"https:\u002F\u002Fwww.zakonik.rs",[139,131,140,141,142,143,144],"Nuxt 4","SQLite FTS5","Google Gemini","Nuxt UI v4","Vercel","Upstash Redis","projects\u002Fzakonik","Ask in plain Serbian, get the article",[148,149,150,151],"RAG","Search","Legal tech","Nuxt","rOtpVkNDOC4TRhiuhF9sh6FNqcdA_tT3IK5WoXUTIfU",1786623948629]