← All workCase study · SaaS dashboard

Kiwici

A CI/CD platform. Users connect a GitHub, GitLab or Bitbucket repository, set up environments and deploy. I built the dashboard and contributed to the API and the Git gateway behind it.

Role
Frontend lead, with backend contributions
Timeline
Apr 2024 – now
Team
Purple Network backend team
Live
dashboard.kiwici.cloud

Problem

Purple Network built Kiwici for teams and agencies that ship sites and apps without a DevOps engineer. A user connects a Git repository, sets up environments with variables, domains and databases, and runs build and deploy. Builds run on Jenkins and environments run on Kubernetes, but the user never touches either of them.

The dashboard covers the whole flow. It has projects and environments, build pipelines with live logs, rerun and cancel, databases and SFTP users, Git provider connections, team invites with roles, billing, and an AI assistant that streams its answers.

Architecture

The dashboard is a React single-page app. It talks only to the Kiwici API, built with Symfony and API Platform, using a Cognito token. The API stores data in MySQL, starts builds on Jenkins, manages environments on Kubernetes and bills usage on Stripe. Git access goes through a separate Laravel service, the Git gateway. It handles OAuth with GitHub, GitLab and Bitbucket and returns repositories, branches and clone credentials.

The dashboard calls the Kiwici API with a Cognito token. The API uses MySQL, Jenkins, Kubernetes and Stripe, and reaches GitHub, GitLab and Bitbucket through the Git gateway.DashboardReact · TanStack QueryZod on every inputKiwici APISymfony · API PlatformI contributedGit gatewayLaravel · OAuthpartly mineGit hostsGitHub · GitLabBitbucketJWTCognitologin · signupMySQLbackend teamJenkins · K8sbuilds · environmentsStripebillingbuilt by meowned by others
The dashboard talks only to the Kiwici API. Git access goes through the gateway, which handles OAuth with the three providers and returns repositories, branches and clone credentials.

Scope

Mine

  • The dashboard, almost all of it (1,106 of 1,257 commits), and the landing page
  • The frontend architecture and the tests that enforce its rules
  • The end-to-end stack and suite
  • Part of the Git gateway: repository listing for the three providers, the GitHub App installation, grants per connection
  • API changes for the Git flow, environment creation and user visibility across shared workspaces

Other teams

  • Most of the API, the data model and billing (Purple Network backend team)
  • Build and runtime infrastructure: Jenkins and Kubernetes

Key decisions

Enforce the frontend rules with tests

Why. The codebase has several authors and grew over three years. The rules are simple: the view renders, a controller hook decides, a service talks to the API, and every external input is parsed with Zod. Architecture tests check them. Each test counts violations per file and compares them with a list of known debt. A new violation fails. A fixed one also fails until the list is lowered, so the list can only shrink. Automated reviews by AI agents check the same rules before each commit.

Trade-off. The checks are regex scans, so code review still has to catch the edge cases. The known-debt lists are visible debt that someone has to pay down.

Hermetic end-to-end tests

Why. Signup, invites, payment and Git connections cross many services. The end-to-end stack runs the real frontend, the real API and MySQL with all migrations. Only third parties are simulated: Cognito, Stripe, Jenkins and the Git gateway. The tests run offline, without keys, and give the same result every time.

Trade-off. Some things can only be checked on the real services, such as 3-D Secure and email delivery. The first start runs 90 migrations and takes 5 to 7 minutes.

One gateway for three Git providers

Why. GitHub, GitLab and Bitbucket each have their own OAuth flow, tokens and API. The gateway hides the differences behind one REST interface, and the dashboard only talks to the Kiwici API. Adding a provider or changing a token flow doesn’t touch the frontend.

Trade-off. It is one more service to deploy and monitor, and its errors have to reach the user with a clear reason. The API passes the gateway’s error through to the dashboard.

In code

An architecture test that can only get stricter, simplified:

zod-usage.test.tsillustrative
import known from "./zod-usage.known.json";

describe("Zod usage — ratchets", () => {
  CHECKS.forEach((check) => {
    it(check.name, () => {
      const actual = check.counts(sources); // violations per file
      const allowed = known[check.name] ?? {};
      const report: string[] = [];

      for (const file of new Set([...Object.keys(actual), ...Object.keys(allowed)])) {
        const n = actual[file] ?? 0;
        const limit = allowed[file] ?? 0;
        if (n > limit) report.push(`${file}: ${n}, ${limit} allowed — ${check.rule}`);
        if (n < limit) report.push(`${file}: ${n}, list says ${limit} — lower it`);
      }

      expect(report.join("\n")).toBe("");
    });
  });
});

Results

157
tagged releases of the dashboard
~2,500
unit tests, including the architecture checks
32
end-to-end tests on a hermetic stack

The dashboard has shipped 157 tagged releases. It has about 2,500 unit tests and 32 end-to-end tests. I can’t share usage numbers.