Tyagi // Engineering Log
← Back to Projects
Admin Platform production

Runo Admin Web Migration

Contributed to migrating Runo's admin web surface to Flutter Web, building reusable components for bulk upload/deletion, integration dialogs, and sheet validation.

Date 2026-01
Reading Time 3 min read
Difficulty
Stack
Flutter Web,Dart,Admin UI +2
Reported

150+

Admin-web related commits

Reported

Bulk upload, deletion, downloads, API keys, integrations

Workflows covered

Sanitized from private commit history; scope is described as “contributed to” rather than “led,” since exact ownership boundaries aren’t independently confirmed.

Problem & Context

Admin tasks — bulk upload, deletion, downloads, API keys, integrations — were spread across older page and component structures with significant duplicated state-handling: process data, templates, column mappings, user lists, loading states, and validation logic each reinvented per page.

Solution & Architecture

The work built a shared set of admin-web primitives so each workflow could compose them instead of duplicating state logic.

Diagram source (mermaid)
flowchart LR
  A[AdminWebHeader] --> B[BulkUploadPageWeb]
  A --> C[DeletionPageWeb]
  A --> D[ApiKeysPageWeb]
  A --> E[IntegrationsPageWeb]
  B --> F[CommonUploadStep]
  B --> G[TemplateUploadWeb]
  C --> F
  F --> H[SheetValidationUtil]

Notable components:

  • CustomStepper / TemplateUploadWeb — a shared multi-step upload flow used by both bulk upload and deletion pages.
  • SheetValidationUtil — centralizes mandatory-field and max-field-size validation against DefaultCrmColumns/StandardFieldName, so upload validation rules live in one place rather than per-page.
  • AdminWebDropdown / AdminWebDropdownMultiple — reusable searchable dropdowns replacing one-off CustomSelector implementations.
  • Cached process data — bulk upload pages were refactored to reuse cached process/user/reportee lists rather than re-fetching on every step, reducing loading-state churn.

Key Decisions & Tradeoffs

Several admin-web pages were rebuilt as

StatefulWidgets with explicit loading/error states rather than relying on simpler stateless patterns — a deliberate tradeoff favoring predictable UX over minimal boilerplate, given how much state these flows juggle (selected columns, template options, dirty-check confirmations).

  • Introduced a “dirty check” pattern (wrapDialogWithDirtyCheck, safeGetIsDirty) across admin dialogs to prevent silent loss of in-progress edits — a direct response to early versions where users could navigate away mid-edit without warning.
  • Standardized on typed integration classes instead of a single IntegrationEnum, trading some verbosity for stronger type safety across Facebook/Meta, WABA, HubSpot, Zoho, and Salesforce integration dialogs.

Implementation Details

The migration moved admin_web models, services, and components into a features directory structure, and later to a feature-folder codebase reorganization — routine but necessary work to keep a growing admin surface navigable as more integrations and workflows were added.

Lessons Learned

Reusable upload/validation components paid off directly: once SheetValidationUtil and CommonUploadStep existed, adding allocation-specific or customer-specific upload variants meant extending shared logic rather than duplicating it — the kind of investment that’s easy to skip under deadline pressure but compounds over many admin workflows.

The dirty-check pattern in particular generalized beyond its original motivation: once wrapDialogWithDirtyCheck existed as a reusable wrapper, it became the default for any new admin dialog holding meaningful in-progress state, not just the upload flows it was first built for — a reminder that a fix scoped narrowly to one bug report is often worth generalizing immediately if the underlying pattern (unsaved state, accidental navigation away) is common across a whole surface rather than specific to one page.