Ivan Tkachuk

Curriculum vitae rev. Oct 2026

UA

Ivan Tkachuk

Senior Frontend Engineer · React, TypeScript

The one who actually delivers. I build product frontends and own them end to end: architecture, design systems, performance and tests.

Kyiv, Ukraine · Open to remote · English C1

CV (PDF)
from an empty repository to production
6 months
smaller entry bundle, 3,232 → 1,390 kB
−57%
components in the design system I built
165
Playwright and Vitest tests
380 + 448

About

I'm a frontend engineer with five years of commercial experience in React and TypeScript. For the last three years I've been the main frontend engineer on two products: a fuel-logistics platform at Reliqio.AI, which I took from an empty repository to production in six months, and a document-workflow platform at Argus Soft, where I was the only frontend engineer for two and a half years.

I trained as an electronics engineer, and the habit stayed with me: measure first, find the root cause, then fix. The deep dives below are written the same way: what the problem was, what I changed and what came out of it.

I work with Claude Code every day through a toolchain I built and tested myself, so agent-written code goes through the same checks as mine. I've also worked full-stack on Node.js and NestJS.

Experience

Each role opens into deep dives with the details that don't fit on a one-page CV.

  1. Jan 2026 — Present
    Kyiv

    Senior Frontend Engineer · Reliqio.AI

    Fuel-logistics platform for booking, dispatch, customs clearance and ordering, used by 5+ logistics companies. Main frontend engineer, about 70% of all commits. I represented engineering to clients and coordinated frontend work with the backend team and part-time contributors.

    • Took the web app from an empty repository to production in six months with continuous deployment: every merge went straight to production.
    • Cut the entry bundle by 57%, from 3,232 to 1,390 kB.
    • Built and own the 165-component design system the whole app runs on, and the test setup of 380 Playwright and 448 Vitest tests.
    • React 19
    • TypeScript
    • Vite
    • Nx
    • Tailwind CSS 4
    • Radix UI
    • Base UI
    • TanStack Query
    • Zustand
    • Playwright
    • Vitest
    • Sentry
    • Mapbox GL
    From an empty repository to production6 months

    Context

    January 2026: a platform for fuel logistics (booking, dispatch, customs clearance, ordering) starting from an empty repository. A small team: me full-time on the frontend, a backend team and part-time contributors.

    What I did

    • Split the app by feature: 16 feature modules behind 22 routes. Each feature lives in its own folder; everything shared lives in the design system.
    • React 19 with React Compiler, TypeScript and Vite in an Nx monorepo set up for a second app. The compiler is on in production builds and off under Vitest, which roughly halves test start-up.
    • Built the design system alongside the features, so new screens were assembled from ready parts instead of one-off markup.
    • Typecheck and lint on every commit; a full lint and a merge-conflict check before every push.
    • Continuous deployment: every merge went straight to production, with errors tracked in Sentry.

    Result

    In production after six months and used by 5+ logistics companies. About 70% of the frontend's commits are mine.

    Entry bundle cut by 57%3,232 → 1,390 kB

    Problem

    The first load was heavy: 3,232 kB of JavaScript (962 kB gzipped) before the app could start. The Mapbox GL map engine weighed more than the rest of the app, and code splitting wasn't taking effect.

    What I did

    • Measured what carried the weight before changing anything.
    • Moved Mapbox GL and the file-upload widget behind React.lazy and dynamic import(): most screens never need them.
    • Found the real blocker: an import cycle between the UI and icon barrel files stopped the bundler from splitting code. Breaking the cycle made the split work.
    • Pointed the design system's exports at the lazy wrappers and renamed the real map component with a warning comment, so an IDE auto-import can't pull the library back into the entry bundle.
    • Prefetched the map on browser idle time (requestIdleCallback, with a setTimeout fallback for Safari), so screens with the map still open quickly. A failed prefetch is caught instead of surfacing as an error.
    • On the Node server: compression, a one-year immutable cache for hashed files and no-cache for index.html, so a deploy reaches users right away.
    Entry bundle before and after, drawn to one scale

    Result

    Entry bundle 3,232 → 1,390 kB (−57%), 962 → 446 kB gzipped. The map still loads, but off the critical path.

    A design system the whole app runs on165 components

    What's in it

    165 React components exported from 80 modules. 60 wrap Radix UI primitives and 17 are built on Base UI, which has the combobox Radix lacks. The headless libraries bring behavior and accessibility (keyboard, focus, ARIA); the system adds Tailwind CSS 4 styling and one consistent API, the same idea as shadcn/ui.

    The other 88 range from primitives to charts and a DataTable, including wrappers for react-day-picker, Embla, Recharts, Sonner and react-virtuoso.

    Where the 165 components come from
    • 60 on Radix UI
    • 17 on Base UI
    • 88 other

    Decision

    When large tables outgrew ordinary scrolling, the obvious fix was to replace the ScrollArea primitive with a virtualized list. That would have split the system into two ways of scrolling and forced screens to migrate.

    Instead I patched ScrollArea to forward a ref to its Radix Viewport, so react-virtuoso runs on top of the existing component. DataTable got an opt-in virtualized mode, turned on where lists are longest; the combobox search list uses the same approach.

    Result

    One scrolling component across the app, fast long lists, and no screen had to migrate.

    Checks for continuous deployment380 + 448 tests

    Problem

    With continuous deployment a regression reaches clients within minutes, so quality had to hold automatically.

    What I did

    • 380 Playwright tests in 77 files, built around real user flows.
    • 61 visual-regression baselines on the most state-heavy dialog, where the layout broke most often. Screenshot tests are expensive to maintain, so they live where the states multiply.
    • 448 Vitest tests in 67 files for logic and utilities.
    • Typecheck and lint on every commit; a full lint, a merge-conflict check and a guard against demo data before every push.
    • Sentry in production: render errors through an ErrorBoundary, API errors through captureException.

    Result

    A safety net on every commit and push, and a test suite that follows how people actually use the product.

    Charts and multi-step forms on a shared basea component per scenario

    Context

    The first charts and multi-step forms were built by other developers, and the number of scenarios kept growing.

    What I did

    I maintained and refactored them first, then moved the shared logic, states and styling into a common base with one small component per scenario.

    Decision

    Why not one configurable component: the scenarios differed in behavior, not just in fields, so a universal component would have grown a condition for every case.

    Result

    A new scenario starts from the base and adds only its differences, and changing one scenario doesn't touch the others.

  2. Jul 2023 — Jan 2026
    Kyiv

    Frontend Developer, sole frontend engineer · Argus Soft

    Document-workflow platform used by thousands of users. I owned the customer app and the admin panel end to end and worked through requirements directly with clients.

    • Replaced hand-written API calls with a client generated from Swagger: 250+ typed methods, with contract changes caught at build time.
    • Built complex forms with conditional logic and dynamic validation in Formik and Yup, orchestrated with Redux-Saga.
    • Integrated an embedded PDF SDK for viewing and annotation, and built drag-and-drop document assembly on react-dnd.
    • React
    • TypeScript
    • Redux Toolkit
    • Redux-Saga
    • MUI
    • PrimeReact
    • Formik / Yup
    • react-dnd
    • Swagger codegen
    • SCSS
    • GitLab
    An API client generated from Swagger250+ typed methods

    Problem

    API calls were written by hand inside useCallback hooks across components, with no shared types. Every backend change risked a silent mismatch.

    What I did

    Replaced them with a client generated from the backend's Swagger (OpenAPI) specification. 250+ typed methods regenerate with one command whenever the backend changes.

    Result

    A contract change shows up as a TypeScript error at build time instead of a bug in production. A whole class of integration bugs went away.

    Complex forms and documentsFormik · Yup · Redux-Saga

    Forms

    Forms with conditional logic, dynamic validation and fields that depend on each other. Validation rules lived in Yup schemas and the asynchronous flow in Redux-Saga, so a change in business rules landed in a schema and a saga instead of spreading across components.

    Documents

    An embedded PDF SDK for viewing and annotating documents in the browser, drag-and-drop document assembly on react-dnd, and live updates over Server-Sent Events.

    Clients

    Requirements came straight from clients, and I worked them through myself, from the first question to the release.

  3. Feb 2022 — Jun 2023
    Kyiv

    Frontend Developer · Smarttek

    • Delivered five client projects, often in parallel: estimates, code review, reusable component libraries built from legacy UI.
    • Worked through requirements with clients in English; also worked full-stack on Node.js and NestJS.
    • Ktizo, an AI music-search product: built the frontend and integrated the client's AI API.
    • React
    • Next.js
    • TypeScript
    • NestJS
    • Material UI
    • i18n
    • Nx
  4. Aug 2021 — Feb 2022
    Kyiv

    Frontend Developer · SoftServe

    • Zenoss (enterprise infrastructure monitoring) and Dokazovo: raised Zenoss frontend unit-test coverage to about 30% with Mocha and Chai; code review and reusable components in English-speaking teams.
    • React
    • Redux
    • Vue
    • GraphQL
    • SCSS
    • Mocha
    • Chai
    • Jest

AI-assisted engineering

I use Claude Code every day in production work, through a toolchain I built and tested myself.

  • 15 custom commands and skills across the whole cycle: getting to know a repository, probing the backend contract before writing code, a pre-PR audit, PR descriptions, saving and restoring session context.
  • 11 lifecycle hooks that enforce review rules as code: unsafe git operations and unapproved pushes are blocked, and the agent can't mark work done without two clean verification runs.
  • About 7,900 lines of Node tooling, covered by about 9,900 lines of tests.
  • AI in product work: contributed to Reliqio's in-app AI assistant; integrated a client's AI API into Ktizo at Smarttek.
A task, end to end, through the toolchain15 commands · 11 hooks

Steps

  1. Onboarding: a command maps a repository's structure, conventions and scripts before the first change.
  2. Contract check: the backend contract is probed before UI work starts.
  3. Guardrails: a git gate blocks destructive operations and pushes without approval.
  4. Verified, not claimed: a stop gate refuses to call work done until the checks pass twice in a row.
  5. A pre-PR audit, then a PR description written from the diff.
  6. Context saved, so the next session picks up where this one ended.

Rule

Agent-written code goes through the same checks as mine. Architecture boundaries, security and the final review stay with me.

Figma to code: when AI gets it wrongroot cause first

Problem

An AI pipeline from Figma to code kept producing layouts that didn't match the designs. The easy verdict was that the model was bad.

What I did

Traced it upstream. The design files were unstructured, and the design tokens for color, spacing and type didn't map to code at all. The inputs weren't going to change, so I built those screens directly from the designs.

Result

The screens matched the designs, and I kept a rule: AI speeds up a process only when its inputs are clean.

Skills

Core
React 19 (incl. React Compiler) · TypeScript · JavaScript · Next.js · also Vue, Node.js, NestJS
UI & styling
HTML · CSS · Tailwind CSS 4 · Radix UI · Base UI · shadcn/ui · MUI · PrimeReact · Sass/SCSS · BEM
State & data
TanStack Query · Zustand · Redux Toolkit · Redux-Saga · React Hook Form · Formik / Yup · REST · GraphQL · Swagger / OpenAPI codegen
Testing
Playwright (end-to-end, visual regression) · Vitest · Testing Library · Jest · Mocha / Chai
Tooling
Nx · Vite · pnpm · ESLint · Husky · Sentry · Git · Claude Code

Background

My father and my brother are engineers. My father taught me to solder when I was five, and at school we built electronic boards together. I studied at a math-focused lyceum, scored 197.5 out of 200 on the national math exam, took part in math and physics olympiads and placed 4th at the All-Ukrainian Young Physicists' Tournament.

Then Kyiv Polytechnic Institute and a master's in micro- and nanoelectronics. After university I moved into web development and kept the engineering habit: measure first, find the root cause, then fix.

Education
Master's Degree in Engineering, Micro- and Nanoelectronics · Kyiv Polytechnic Institute (KPI) · 2020
Languages
English C1 · Ukrainian (native) · German A2

Contact

Open to senior frontend and engineering leadership roles, remote or in Kyiv. Email and Telegram are the fastest ways to reach me.

Plain HTML, no framework, no tracking. Set in Barlow and JetBrains Mono. Revised October 2026.