[{"data":1,"prerenderedAt":405},["ShallowReactive",2],{"project-case-hector":3},{"blocks":4,"person":342},[5,16,34,98,114,131,160,273,305,329,335],{"component":6,"data":7},"ProjectCaseHeader",{"index":8,"subtitle":9,"name":10,"headline":11,"description":12,"metaDescription":13,"url":14,"tone":15},"02","MOBILE APP · PERSONAL PROTECTION","Hector","An iOS and Android app with the iOS build in the cloud","An app for booking personal protection and a car with a driver: live tracking of the team on a map and an SOS button. The service operates in Europe and the UAE.","An app for booking personal protection and a chauffeured car: live tracking of the team on a map and an SOS button. The service operates in Europe and the UAE.","https:\u002F\u002Fhectorvip.com\u002F","primary",{"component":17,"data":18},"ProjectCaseCover",{"image":19,"imageMobile":20,"imageAlt":32,"frame":33},null,{"src":21,"width":22,"height":23,"variants":24},"\u002F_nuxt\u002Fpreview-mobile.b4yGJ-pG.webp",600,1304,[25,28,31],{"src":26,"width":27},"\u002F_nuxt\u002Fpreview-mobile.CJLUUGVg.webp",160,{"src":29,"width":30},"\u002F_nuxt\u002Fpreview-mobile.BAQF9lQh.webp",300,{"src":21,"width":22},"Screens of the Hector app: choosing protection, live tracking of the team, order review and the SOS screen","devices",{"component":35,"data":36},"ProjectCaseMeta",{"tags":37,"stack":40},[38,39],"Mobile app","Team Lead",[41,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96],{"label":42,"own":43},"Vue 3",true,{"label":45,"own":43},"TypeScript",{"label":47,"own":43},"Vite",{"label":49,"own":43},"Capacitor 8",{"label":51,"own":43},"Pinia",{"label":53,"own":43},"Vue Router",{"label":55,"own":43},"vue-i18n",{"label":57,"own":43},"VueUse",{"label":59,"own":43},"Tailwind CSS 4",{"label":61,"own":43},"vee-validate",{"label":63,"own":43},"zod",{"label":65,"own":43},"maska",{"label":67,"own":43},"Vitest",{"label":69,"own":43},"Vue Test Utils",{"label":71,"own":43},"Storybook",{"label":73,"own":43},"oxlint",{"label":75,"own":43},"ESLint",{"label":77,"own":43},"oxfmt",{"label":79,"own":43},"Husky",{"label":81,"own":43},"mise",{"label":83},"Leaflet",{"label":85},"OpenStreetMap",{"label":87},"lottie-web",{"label":89},"View Transitions API",{"label":91},"miragejs",{"label":93},"Codemagic",{"label":95},"TestFlight",{"label":97},"BrowserStack",{"component":99,"data":100},"ProjectCaseFacts",{"facts":101},[102,105,108,111],{"key":103,"value":104},"role","Frontend Team Lead",{"key":106,"value":107},"team","15 people: 5 frontend, 4 backend, 2 designers, 2 QA, a PM and a DevOps engineer",{"key":109,"value":110},"duration","Since June 2026",{"key":112,"value":113},"status","In development, demo build for the client",{"component":115,"data":116},"ProjectCaseRole",{"id":103,"name":117,"items":118},"What I did",[119,122,125,128],{"title":120,"text":121},"Architecture and stack","Chose Vue 3 and Capacitor over React Native, Flutter and native. Wrote the guidelines: Tailwind without separate styles, all text through i18n, no as casts and no comments, tests in the AAA format.",{"title":123,"text":124},"Project start","Set up the project: Capacitor with live reload, Tailwind, Storybook, linters, pre-push, the environment via mise, a README on running it in the emulator and the simulator.",{"title":126,"text":127},"Code","Time and date wheels with seamless looped scrolling, a phone mask, an accordion, i18n with the device locale, the first versions of the order review and active order screens.",{"title":129,"text":130},"The team and the client","Set tasks and priorities for four more developers, reviewed and merged PRs. They wrote the animations, the iOS part, the signals and the maps from my tasks. I presented the app to the client myself.",{"component":132,"data":133},"ProjectCaseChapter",{"id":134,"name":135,"blocks":136},"context","Context",[137,149],{"component":132,"data":138},{"id":139,"name":140,"blocks":141},"business","Business",[142,146],{"component":143,"data":144},"ProjectCaseText",{"text":145},"Hector is a personal protection and chauffeur service in Europe and the UAE. A client books protection or a car, pays for the order, follows the team on a map and presses SOS in a dangerous situation.",{"component":143,"data":147},{"text":148},"Protection agents have their own account: duty status, assigned orders and the route, arrival check-in, SOS alarms from clients.",{"component":132,"data":150},{"id":151,"name":152,"blocks":153},"situation","The situation",[154,157],{"component":143,"data":155},{"text":156},"There was no app: this is a new product built from scratch to the client's specification since June 2026.",{"component":143,"data":158},{"text":159},"The client needs everything to work on iOS and Android. The team works remotely from different cities, each on their own work computer, and an iPhone and a Mac are not part of a developer's core set of devices. So the iOS build and testing had to be set up so they would not depend on the devices at hand. On top of that the app works with device APIs, for example it reacts to a double tap on the back of an iPhone.",{"component":132,"data":161},{"id":162,"name":163,"blocks":164},"process","Process, architecture and decisions",[165,186,194,226,237,245],{"component":132,"data":166},{"id":167,"name":168,"blocks":169},"flow","The order flow",[170],{"component":171,"data":172},"ProjectCaseSteps",{"steps":173},[174,177,180,183],{"title":175,"text":176},"Configurator","Protection: the number of agents, armed or not, dress code, a K9 handler, language. Or a car with a driver. Route, date, duration. In development: order state in Pinia, date and time wheels with seamless scrolling, address search through Photon and Nominatim, so the client puts an order together on one screen in a couple of minutes",{"title":178,"text":179},"Review","The client checks the order and sends it to a manager. In development: the order review screen and a phone mask, so the manager gets the order with no mistakes in the contacts",{"title":181,"text":182},"Payment","An itemized invoice, a status timeline, the order can be edited. In development: the active order screen with a status timeline. Editing an order takes the client back to the configurator with the data kept, so they do not build the order again",{"title":184,"text":185},"Live tracking","The team on the map with ETA, the SOS button at hand. In development: a Leaflet map and a route through OSRM. The SOS button is pinned to the screen, so there is no need to look for it in a dangerous situation",{"component":132,"data":187},{"id":188,"name":189,"blocks":190},"stack-choice","Why Vue and Capacitor",[191],{"component":143,"data":192},{"text":193},"The stack was chosen around the team's strengths. The frontend developers are confident with Vue, and I know its ecosystem well myself. So from day one the team built the product instead of learning a new tool: this shortened the road to the demo and lowered the risk of errors. Vue with Capacitor brings other advantages too: one codebase for iOS and Android, an interface in plain CSS that lets Hector's complex design with gradients, glass and transparency match the mockups exactly, and access to the phone's features through plugins: biometrics, secure storage, system buttons. React Native and Flutter were considered too, but they would have given less freedom in the layout and taken time to learn.",{"component":132,"data":195},{"id":196,"name":197,"blocks":198},"architecture","Architecture",[199,223],{"component":200,"data":201},"ProjectCaseArchitecture",{"items":202},[203,207,211,219],{"label":204,"title":205,"text":206},"APP","Vue 3 SPA","Inside a Capacitor shell on Android and iOS. Pinia keeps the order, the configurator, assigned orders and signals.",{"label":208,"title":209,"text":210},"NATIVE","Native plugins","Biometrics, secure storage for the PIN, the keyboard, the system Back button, safe area for iPhone cutouts.",{"label":212,"title":213,"text":214,"code":215},"API","Backend REST","Its own HTTP client with interceptors. In the demo a miragejs mock server that follows the contract stands in for the API.",[216,217,218],"auth","configurator","orders",{"label":220,"title":221,"text":222},"MAPS","Maps","OpenStreetMap: Leaflet for the map and live tracking, Photon and Nominatim for address search, OSRM for the route.",{"component":143,"data":224},{"text":225},"All text goes through vue-i18n and the locale comes from the device, so a new language is one more file. The mock server follows the API contract, and neither the frontend nor the demo waits for the backend.",{"component":132,"data":227},{"id":228,"name":229,"blocks":230},"signals","Silent signals",[231,234],{"component":143,"data":232},{"text":233},"A client assigns actions to a double and a triple tap on the back of an iPhone and to four buttons of a lock screen widget. Each signal sets who receives it, what to do and how fast to react: the bodyguard comes over, the driver brings the car, the dispatcher is only informed.",{"component":143,"data":235},{"text":236},"The app catches a signal through a URL scheme, including on a cold start. It is a prototype for now and is hidden in the demo build.",{"component":132,"data":238},{"id":239,"name":240,"blocks":241},"security","Data security",[242],{"component":143,"data":243},{"text":244},"Signing in to the app is protected the way banking apps do it: with a PIN, Face ID or a fingerprint. The PIN is kept in a protected part of the phone that other apps cannot reach. If the session expires, the app returns to the sign-in screen by itself, so a stranger cannot keep working in someone else's account. Clients and protection agents see only their own sections.",{"component":132,"data":246},{"id":247,"name":248,"blocks":249},"quality","Quality and releases",[250,253,264,267,270],{"component":143,"data":251},{"text":252},"Vitest and Vue Test Utils: 177 files, about 1,700 test cases next to the code, including the session, PIN, safe area and keyboard services. Storybook with accessibility checks. Another developer writes Playwright E2E tests for the demo scenarios: sign-in, an order from the configurator to completion, SOS, the profile, the agent account.",{"component":254,"data":255},"ProjectCasePipeline",{"stages":256},[257,260,262],{"name":258,"next":259},"pre-push","tests and lint of changed files",{"name":93,"next":261},"iOS build in the cloud",{"name":95,"next":263},"the app on an iPhone",{"component":143,"data":265},{"text":266},"I set up the pre-push with tests and lint and the iOS build in Codemagic. Android is built as a signed bundle from Android Studio.",{"component":143,"data":268},{"text":269},"We work in one-week sprints with a demo at the end of each. Tasks live in Jira, one branch per task, PRs with review.",{"component":143,"data":271},{"text":272},"For the client this process means predictability. Every week they see a working version at the demo and can correct the course before a mistake gets expensive. The cloud iOS build and TestFlight make it possible to show the app on a real iPhone at any moment. For development, tests next to the code and the pre-push check catch errors before review, and five developers change shared code in parallel without breaking each other's work.",{"component":132,"data":274},{"id":275,"name":276,"blocks":277},"challenges","Challenges",[278],{"component":279,"data":280},"ProjectCaseChallenges",{"items":281},[282,295],{"id":283,"name":284,"rows":285},"ios","The iOS build in a distributed team",[286,289,292],{"key":287,"text":288},"problem","Everything has to work on iOS. The team is spread across different cities and works on its own devices, and an iPhone and a Mac for building and debugging are not part of a developer's work set. We needed a way to build and test iOS without being tied to a specific device.",{"key":290,"text":291},"solution","The iOS build moved to the Codemagic cloud: a simulator variant and a signed .ipa uploaded to TestFlight. A script patches the native project on every build, otherwise an app built with Xcode 27+ closes right after launch. We tested on BrowserStack cloud devices and through TestFlight.",{"key":293,"text":294},"result","The app reached a demo on iOS and Android. The demo build fixed bugs that show up only on an iPhone: a double tap zoomed the screen, the header slid under the status bar, the bottom of a sheet went under the home indicator.",{"id":296,"name":297,"rows":298},"visual","Complex visuals in a WebView",[299,301,303],{"key":287,"text":300},"Gradients, transparency, a glass header, a parallax sheet, transitions between screens: on iOS visuals like these keep breaking.",{"key":290,"text":302},"The intro was made with Lottie instead of a video: the video was heavy and lost quality. Transitions are built on the View Transitions API. While preparing the demo we gathered the visual bugs into one list and closed them one by one.",{"key":293,"text":304},"Of about 20 visual bugs in the demo, all but one are closed. The interface in a WebView looks like a native app.",{"component":132,"data":306},{"id":307,"name":308,"blocks":309},"results","Metrics and results",[310,326],{"component":311,"data":312},"ProjectCaseResults",{"stats":313},[314,317,320,323],{"value":315,"label":316},"26","screens for the client and the agent",{"value":318,"label":319},"~1,700","test cases in 177 files",{"value":321,"label":322},"55","reusable components",{"value":324,"label":325},"2","platforms from one codebase: iOS and Android",{"component":143,"data":327},{"text":328},"The app reached a demo build for the client. There are no business metrics yet: it has not been released.",{"component":330,"data":331},"ProjectCaseCta",{"id":332,"title":333,"text":334,"url":14},"cta","Need an iOS and Android app with a complex design?","I will pick a stack that fits the team, set up the project and bring the app to a demo on both platforms.",{"component":336,"data":337},"ProjectCaseNext",{"slug":338,"name":339,"index":340,"tone":341},"brendmart","Brendmart","03","secondary",{"name":343,"alternateNames":344,"role":347,"skills":348},"Michael Malakhov",[345,346],"Михаил Малахов","Mikhail Malakhov","Fullstack Developer",[349,45,350,351,352,353,354,355,356,53,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,47,376,377,378,379,380,381,67,382,383,384,71,385,386,387,388,389,390,391,392,393,51,394,395,396,397,398,399,400,401,402,403,404],"JavaScript (ES6+)","HTML5","CSS3","Sass","Vue.js (Vue 2\u002FVue 3, Composition API)","Nuxt 3\u002F4","React","Next.js","Tailwind CSS","Bootstrap","SPA","SSR","MPA","SEO","Chrome DevTools","Node.js","NestJS","Express","Laravel","Rust","PostgreSQL","Redis","REST API","Docker","Nginx","CI\u002FCD","GitHub Actions","Webpack","Git","GitHub","GitLab","Figma","Redux","Jest","Tauri","Prometheus","Jira","Claude Code","Codex","Gemini","MCP","Atlassian Rovo","Zustand","SWR","TanStack Query","Vuex","React Hook Form","Zod","Capacitor","GraphQL","WebSocket","Clinic.js","autocannon","Lighthouse","GitHub Copilot","AI agents",1793309471088]