Rewriting a FinTech consulting tool: web, Windows and macOS from one Flutter codebaseFinton rewrite with Flutter
2026-11-14
by Pan Friebe
13 min read
For Finton I rebuilt the internal tool that their daily client business depends on as a Flutter app. It runs from one codebase on web, Windows and macOS. This article covers what lies underneath: why I recommended a full rewrite instead of a refactor, which architecture decision paid off most after 6 months, where multi-platform Flutter really breaks down, and what an internal tool can do with AI today that it could not do two years ago.

What does Finton do and why is the tool so central?
Finton advises finance departments and delivers its own tool stack alongside, today with a focus on AI solutions for the office of the CFO in mid-sized companies. Service plus software: exactly this model is becoming buildable at a speed that was not possible before. What a few years ago would have been an 18-month platform build is now a production tool in 6 months. That is the real background of the engagement. Not “we are building a tool”, but “we are building the delivery layer of a next-generation consultancy”.
The old tool was a prototype that had grown over time: lag in the core workflow, technical debt, and the team could not take on more clients without the codebase becoming the bottleneck. That was the starting point when Finton came to me. What they needed:
- Data visualisation at a level clients are willing to pay for. Polished, not “functionally sufficient”
- Complex data analysis directly in the frontend, without constant round trips to the backend
- Smooth interaction without stutter, even with larger data sets in the dashboard
- A cleaned-up clean architecture with a feature-first folder structure (Code with Andrea wrote the clearest article on it)
- An AI feature that lets users build dashboards themselves
Rewrite or refactor: which won at Finton?
A mix of both. In the end everything was replaced, but incrementally. The app had to work on every single business day, so a 6-month desk phase was not an option.
In concrete terms we lost speed in the first 2 months: parallel structures, a lot of glue code, sometimes both implementations side by side. That was planned pain, not an accident. In the last 4 months it paid off massively. Feature velocity picked up clearly because the foundation held.
When this mix is the right approach:
- The tool is already in production
- Users depend on it, the workflow runs through it every day
- It has to keep working, no compromise
- There is enough time for foundation work, at least 4 to 6 months
When not: if nobody seriously uses the tool, a classic rewrite is faster. And if the team only has to ship feature output every week, the mix guarantees frustration in the first months.
Which architecture decision paid off the most?
Modularity. If I could highlight only one architecture decision per engagement, it would be this one. At Finton it was the lever that made everything later possible.
One example: the form editor was built as a separate building block from the start. It covers three use cases:
- Entering client data
- KPI definitions by the consultants
- Custom fields in the dashboard
Three use cases, one editor. In a monolithically grown architecture it would have been written three times, maintained three times and drifted apart three times.
The dashboard, too, was designed to be modular from day one. What followed from that, nobody foresaw at the beginning:
- The modular structure allows dashboards to be serialised as JSON.
- With JSON dashboards, users can create new dashboards themselves.
- With JSON dashboards, the AI can generate dashboards too.
- Generated JSON is validated in the app and adopted directly.
Without this modularity the AI dashboard feature would have been a 6-week project. With it, it took one week, because the format already existed and the rendering pipeline could already handle JSON.
Modularity sounds like an architecture textbook. In practice it compounds: every hour you invest in clean building blocks at the start, you save several times over in the months that follow.
Riverpod or Bloc?
In almost all cases Riverpod is enough, and it is the fastest option. A custom Bloc-style structure only pays off if you have reasons that no standard Riverpod setup covers. I have rarely seen those reasons.
More important than the choice of state manager is using Riverpod properly instead of treating it as a “stream wrapped in a provider”: fine-grained providers, derived state as separate providers instead of calculations in the widget, clear dependency chains. If you use Riverpod purely as a stream tool, you could have skipped the library.
The real lever is clean architecture, not the tool underneath. Riverpod is powerful, but without clean layering even the best tool only gives you faster spaghetti.
When does clean architecture become an end in itself? When you introduce layers and interfaces that nobody reads and nobody needs. My heuristic: Is the code enjoyable to read? Do you find what you are looking for just from folder and file names? Is what is inside what you expected? If yes, the dose is right. If not, you have too much or too little structure. Both show the same symptoms.
Why should code look like a table of contents?
Because code structure today is also UX for AI agents. If your repo looks like the table of contents of a book, an agent finds the right file in seconds. If it is a snake of 200 files without a clear hierarchy, it hallucinates paths and writes in the wrong place.
That is why feature-first beats layer-first: lib/features/dashboards/, lib/features/forms/, lib/features/kpi/ instead of lib/widgets/, lib/services/, lib/models/. The definitive article on this in the Flutter ecosystem is by Code with Andrea.
The argument for CTOs: feature-first costs a one-off mental switch. It pays off every day a developer or an AI agent builds something new. Today that is every working day.
Where do AI features in internal tools break down?
In three places: token costs, a missing validation loop, and an architecture that was not built for AI. On top of that comes the question of whether the feature pays for itself at all.
Token costs break first
In the demo the cloud call runs on the most expensive model; in production exactly those calls eat the margin. The levers:
- Caching where results are stable
- Smaller models for sub-tasks without real reasoning
- Local or offline AI for simple classification and extraction
- A hard token budget per feature instead of “let it run”
Rule of thumb: in the worst case every AI call costs a hundred times what you planned, when a user abuses it or an edge case hits. That is not an argument against AI, it is an architectural constraint you have to plan for.
Non-deterministic features need a validation loop
At Finton the AI builds dashboards. A dashboard is a JSON structure the renderer knows. Wrong JSON does not break the tool, it gets rejected. The flow:
- The AI generates JSON.
- The JSON is validated against the schema of the internal format.
- If it is invalid, the error goes back to the AI with feedback and it tries again.
- After two failed attempts the flow stops with a clear message: “That did not work, please phrase your request differently.”
The loop with an exit is the difference between an AI feature for the demo and one that does not destroy user trust. Without an exit you get endless loops and exploding token costs. Without clear feedback the feature feels like gambling.
When does AI in an internal tool pay off?
With a sober cost calculation:
- How many hours per week does the feature save the team?
- What does one of those hours cost internally?
- What does the feature cost per month in tokens, maintenance and edge-case handling?
If the monthly savings are roughly three times the AI costs, the feature is worth it. Below that it is vanity. Realistically it usually pays off later than you think at the start. Plan for that instead of being surprised later.
AI-generated UI needs an architecture built for it
If the AI is later supposed to generate parts of the interface, every affected area has to be serialisable as JSON. That only works with a modular architecture from day one. If you spend three months on a monolithic form engine and then add “the AI should be able to build forms”, you rewrite the form engine. At Finton the dashboard was designed as a JSON structure from the start. That is exactly why the AI dashboard feature was a one-week project and not a six-week one.
“AI-first” is a worn-out term. In practice it means: every component you build should be serialisable, verifiable and described by a schema. Then AI-generated UI is an extension of the existing renderers, not a new project.
Is Flutter the right choice for internal tools?
Yes, for internal tools Flutter is a no-brainer. One codebase, three platforms in production: web, Windows and macOS. That is exactly what internal tools need, because the consulting team works on all kinds of devices and nobody wants to fund three frontend teams.
What one Flutter codebase covers that would have needed two specialists with React and React Native:
- Pixel-perfect data visualisation, identical on all three platforms
- Smooth scrolling in large tables without platform-specific hacks
- Automatic desktop updates without a separate installer team
Where Flutter currently falls short:
- Web SEO remains a construction site. Irrelevant for internal tools, a deal-breaker for marketing sites
- A native iOS look at premium level requires detail work that is cheaper in SwiftUI
- WebViews and complex native embedding are still more cumbersome than they should be
For internal tools none of this matters.
Why do internal tools get slow under load?
Mostly in three places: state that is too large, lists without lazy loading and missing caching. At Finton performance was not a theoretical problem: complex calculations, many rows in the dashboard, everything in the frontend.
- State that is too large and re-renders half the screen on every small change
- Lists without lazy loading that drop a frame per scroll from 1,000 rows on
- Missing caching where it belongs. The same financial data is loaded three times, each time with a spinner
The answer is not magic, it is discipline and a profiler:
- Reducing state on the critical path, fine-grained Riverpod providers
- Lazy loading of modules that are not visible, so a dashboard with 12 widgets does not fire 12 API calls
- Caching recurring financial data in the right places, measured rather than guessed
If your tool stutters and nobody on the team can use the profiler, that is not primarily an architecture problem. It is a seniority problem on the team.
How do you get good UX without a dedicated UX team?
With experience condensed into skills. In an engagement like Finton the interface is created along the way: no UX team included, no four-week workshop phase. I work with a UX skill that draws on the classics:
- Don Norman, The Design of Everyday Things (signifiers, affordances, mental models)
- Steve Krug, Don’t Make Me Think (cognitive load, self-evidence)
- Susan Weinschenk, 100 Things Every Designer Needs to Know About People (limits of perception, attention)
- Nir Eyal, Hooked (for tools used every day, the habit loop matters)
This does not replace a UX team. But it prevents the slowest kind of failure: designs that hit a wall after 2 weeks of use because they were created in one day and nobody had the heuristics in mind.
Which tools save the most time in operation?
Two recommendations from the engagement: Sentry with MCP integration for debugging and Velopack for desktop updates.
Sentry with MCP integration. The AI agent pulls errors live from Sentry, reads the stack trace, builds context from the repo and proposes a fix. The time from a reported error to the fix drops considerably. That matters because web, Windows and macOS on one codebase is not trivial: the platform lifecycles are further apart than mobile developers expect. Desktop lifecycles, file system permissions, CanvasKit quirks on the web.
Velopack for automatic updates. Desktop updates are a pain point nobody thinks they have until the tool has 50 users and nobody installs the new version by hand. Velopack solves this without a separate installer stack: patch deltas, rollback, signing, one pipeline. Once it is set up, the Finton team does not have to take care of anything.
When is a rewrite the right decision?
When the codebase is demonstrably the bottleneck and the team has time for foundation work. In principle almost everything can be improved incrementally. So the right question is not “rewrite yes or no”, but:
- How many users depend on it? Few: a classic rewrite is cheaper and faster. Many: the mix, as at Finton.
- How much time does the team have? Little: no rewrite, just harden the three most painful spots. Plenty: modernisation is worth it.
- Where is the bottleneck really? If feature velocity keeps dropping and new developers need weeks to become productive, the codebase is the limit. If velocity is stable and only the hardware has aged, you do not need a rewrite, you need a profiler.
Anyone who promises you that a rewrite is always right is selling hours. Anyone who says “never” has not yet seen a codebase up close in which every new feature breaks in three places.
How does working together look when a CTO hires me?
The first 30 days belong to the foundation, handover starts on day one, and billing happens in sprints at a fixed price.
The first 30 days
A lot of discovery, architecture planning and restructuring at the base. The first visible output takes longer than with a junior who starts working through feature tickets right away. At Finton the first two months were restructuring that was barely noticeable in the frontend. From month three on, feature velocity picked up strongly because the foundation held.
Anyone who promises quick wins from week 2 is either not building a foundation or fooling themselves.
Handover from day one
From the start I build so that the team can carry on without me:
- Structured, clearly documented architecture
- A wiki in the repo where the team and AI agents look up architecture decisions
- Meaningful commit messages, so decisions remain traceable in 12 months, with or without AI
- Sentry with MCP integration, so the team can find and fix errors on its own
- Release pipelines set up and documented
An engagement is only good once the team no longer needs me.
What working together looks like
At the beginning more time goes into linter settings, architecture decisions and conventions. After that the focus is on coding. Small PRs and clean commits save the most time. The more modular the codebase, the easier it is to split up tasks and the less often developers block each other. Beyond a certain size you need tests so nobody accidentally breaks the work of others.
Realistically it takes 30 to 60 minutes of coordination per day. Anything beyond that is chatter and costs speed.
Scope and price: 2-week sprints at a fixed price
Each sprint has a clear scope and a fixed price. If I misjudge, I keep working for free until the agreed scope is delivered. Exception: new requirements outside my control come in. Then we re-scope together. The risk of the estimate is carried by whoever makes it. That is me.
A small team instead of an agency stack
Everything runs through us: a small team, one point of contact. There are no handovers between agency layers of project management, mid and junior eating margin and context. In the age of AI this matters more than before: context is everything, and it fragments with every handover and as soon as people work on the same codebase in parallel without knowing what the others are up to.
Add to that senior Flutter experience combined with active AI practice. Not “I have a ChatGPT subscription”, but: every week I follow what changes in validation loops, tool use, MCP and sub-agent orchestration, and apply it in client code. That is my only unfair advantage, and I actively maintain it.
Who this is right for: three conditions
- Frontend and backend in one hand. I need at least read access and PR rights on the backend so that performance and data flow decisions stay clean. “You only do frontend here” does not work in a tool used in production.
- The interface is clearly owned. Either there is someone for design on the team I work closely with, or I design along with the build. Half and half is the slowest option.
- Production pressure. My sweet spot is tools used in the business every day. If nobody seriously needs the tool, I am more expensive than necessary. Then hire a junior.
If you are looking for a body-shop developer for tickets, I am the wrong choice. That saves both sides time.
Is your internal tool the bottleneck right now?
Then let us talk. Write to me via the contact form or directly on LinkedIn. A live link or app store link to your tool helps me quickly see where it gets stuck.