A walkthrough of what's being built, what's already decided, and — the part worth sitting with — what's already proven working, not just planned. Six lanes of work. Four of them already have real, verified proof behind them — not descriptions of a plan, things that were built, run, and checked.
Pannell runs an older, standalone time-tracking application — Vue on the front end, Supabase underneath — completely separate from Pannell's main app. The goal: fold it into the main app so there's one system, one login, one database, one codebase. Not two stacks running side by side forever.
There were two honest ways to do this. One was chosen deliberately, on 2026-07-30.
Leave the old backend running as its own black box, put a new login system (AWS Cognito) in front of it, and build a bridge so the main app can talk to it. Fully scoped, kept for reference — but not what's being built.
Move the old backend's actual logic into Pannell's main app, retire Supabase entirely, end up with one unified system. More one-time work up front — no second stack to maintain forever, no bridge to keep alive.
Not a strict assembly line — several lanes run in parallel on purpose. The frontend is independent and starts immediately; the backend is the spine everything else leans on; roles and data-migration ride alongside it; verification is the convergence point; mass-upload is "trigger zero," built last.
The two lanes that carry the actual bulk of the work — frontend and backend conversion — each already have a real, working, verified prototype behind them. Not a description of an approach. A thing that was built, run, and checked. Two more lanes joined them the same way — the role mapping and the data-migration mechanism are both dry-run proven against real, running systems too. The safety gate that closes the project out can't be dry-run the same way yet — it needs the other lanes further along first — but its checking machinery is built and proven, not left for later.
One real screen taken all the way from the old Vue app into Pannell's Mantis/Next pattern — tested, wired up, working end to end. Not a guess at what the conversion would look like.
Vue syntax doesn't copy-paste into React — the screen and the store logic both get rewritten. What carries over is the behavior: the same rules, the same data shape, nothing invented.
migrator/) that picks the next screen and generates a complete conversion promptOne real backend function, ported end to end and checked against the actual, unmodified legacy code — not a code review, an automated diff.
Deno.serve()The strongest reuse claim here is the database itself — same tables, same access rules, same data. Only the connection code changes, and it's written once, not per function.
migrator/) that picks the next function and generates a complete conversion promptThe role-mapping decision, exercised against Pannell's actual account system — not just written down and signed off. This lane also carried the one genuinely open question in the whole package: is "Supervisor" the same job as "General Foreman"? The old app uses 7 role names; Pannell already has its own 8. Most of the mapping was already clear — one pairing wasn't, and it couldn't be resolved from the software. It needed someone who knows how the crews are actually organized. Answer: yes, the same job. Collapse it.
Why it mattered: in Pannell, your role decides how you log in — field employees use their employee ID, office/admin staff use a password. A wrong guess here wouldn't just mislabel someone — it could lock a real person out on day one. That's why this got a real answer instead of a best guess, and why the answer got tested, not just filed.
Everything else in this lane was already decided
The exact mechanism the whole "keep the database's own rules" decision depends on, tested against a real restored copy of the schema.
Lane 05 is the final gate: nothing ships until old and new backend answers match, function by function, role by role, against real data. The gate itself can't run at full scale yet — it needs the backend conversion much further along first. What doesn't need to wait is the checking machinery itself, and that's built, tested, and proven correct now, so it's a plug-in step once conversion catches up, not a second research phase.
Ran a direct scan of all 99 in-scope functions' real source — not a sample, all of them — for whether each one relies on the database's row-filtering or on its own manual access check. Result: 99 of 99 need the manual check verified explicitly. That's the stricter, more thorough test — it doesn't let a function pass just because its answer happens to match the old one; it has to prove it actually turns away someone who shouldn't have access. Better to know that now, before conversion starts at scale, than discover it function by function later.
This lane is deliberately built last — it needs the rest of the system stable first. But "built last" doesn't have to mean "unstarted until then." Rather than wait on a real sample file from the client to know what to build against, we built our own version of that spreadsheet from what's already confirmed, and proved the actual logic against it — for real, against the real backend.
One of our own earlier decisions — match employees by their Paycom ID — turns out not to hold for every employee. It works for field crew, but the real backend quietly clears that ID for office/admin-tier accounts today, since they log in a different way. That's not a guess; it's what actually happened running the real test against the real code, and it's now flagged as a decision that needs revisiting before this ships, not something waiting to be discovered later.
What's still genuinely unbuilt — not just undecided: matching a supervisor's code to their real account, and matching a contract code to its real contract record. Both are real, separate pieces of work the old system itself treats as a later pass, not something the template or this test solves. That's the honest gap between "the upload works" and "everything flows seamlessly into the time tracking module" — the first is proven, the second still needs this piece built.
The two heaviest pieces of this project are frontend and backend conversion — that's where the bulk of the remaining hours live. The risk that normally justifies a discovery phase, a padded estimate, or "we won't know the real cost until we're in it" — that risk has already been retired, on both of the expensive lanes, with working proof. What's left is applying an already-proven pattern through an already-built tool, screen by screen and function by function. That's scoped work, not open-ended exploration. The two lighter lanes — roles and data migration — got the same treatment: not left as plans, exercised against real systems before being called done. And the safety gate that closes the whole project out isn't a research question either — its machinery is already built and tested, ready to run the moment there's more converted code to check. Even the lane that's built deliberately last isn't sitting idle — its template and its core logic are already proven, ahead of when they're actually needed.
This backend calculates and exports payroll. A subtle slip in a rewrite doesn't show up as a crash — it shows up as someone being paid the wrong amount. So the plan treats verification as its own real step, not a formality. The good news: proving it correct is also cheap — but only because it's built to be automatic.
Same real historical data goes into both. The computer compares every answer, for every function, all at once.
A match is proof the rewrite preserved behavior for that case. It costs nothing to confirm.
A timestamp a moment later, a new auto-generated ID, a list in a different order — a person calls that harmless once, and the system recognizes every future instance of that same kind automatically. The work scales with the number of distinct kinds of surprise, not with the number of tests run.
A handful of old functions returned everything to anyone, by design of the old system. For those, matching the old behavior isn't good enough — the new version is checked to confirm it actually turns away users who shouldn't have access. This is the one place the new system is supposed to differ from the old.
What this does, plainly
Pannell's admin app exists today as a React web app — pages that run in a browser. This takes each of those screens and rebuilds it as a React Native app — a real, installable app that runs natively on an iPhone or Android phone, not a browser view of the website.
Pannell's admin app is roughly 50 pages. Three of them were converted end to end and are running on the iOS Simulator right now — a real, working prototype, not mockups or static screens.
What's actually unique here: a mobile rewrite sounds like rebuilding the whole app twice. It isn't. Only one layer — the visible screen — actually gets rebuilt. The two layers underneath it, the ones that are expensive to get wrong, move over almost unchanged:
Only the top layer changes per screen. The bottom two layers — the part that's actually hard to get right — are already proven and move over for free.
A list screen with add / edit / delete. The most common page shape in the app.
Conditional fields, real validation, an async-loaded dropdown — the hardest shape, proven.
KPI cards and charts, rebuilt with a native charting library — no web-only chart libraries used.
These three cover essentially the whole app — every remaining page is a list, a form, a detail view (a subset of what the form canary already proves), or a dashboard.
Easy to fake a nice-looking demo. This isn't one. Every screen's data flows through the same hook
logic and the same real API endpoints your NestJS backend already exposes — GET /regions,
POST /users,
etc. — with a one-line switch to flip from an in-memory mock to the live server. Tapping Add / Edit /
Delete really runs the create/update/delete path.
A second pass re-skinned all three canaries with a proper component library (shadcn-style, the same
idea used on modern web apps) themed with the app's actual green brand color — not a generic default
look. Verified clean (tsc --noEmit)
and running on the simulator.
Two different things, and it's worth being precise about which is which. The conversion mapping is proven and frozen — how a web screen becomes a native screen is a known, repeatable recipe now, not something to figure out per page. The automation that runs that recipe at scale is planned, not yet built — a small tool that would batch-convert the remaining ~47 pages through a cheaper model, with the cost-saving levers (prompt caching, batch pricing) already identified and costed out. Building the automation before proving the rhythm by hand is the wrong order anyway — the plan is to hand-convert the next 5–10 pages first, confirm it stays this cheap and repetitive, then automate the rest. That's a 30-second approval checkpoint when the time comes, not a research phase.
Same reason as the four proven Path B lanes above: the risk that normally justifies "we won't know what mobile costs until we're in it" has already been retired, with working proof — running on a real simulator, wired to real endpoints, styled in the real brand. When mobile comes up later, it's applying an already-proven recipe to the remaining pages, not scoping a new project from zero.